0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
generator.h
Go to the documentation of this file.
1/**
2 * @file src/common/generators/plantuml/generator.h
3 *
4 * Copyright (c) 2021-2025 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18#pragma once
19
23#include "config/config.h"
24#include "util/util.h"
25#include "version/version.h"
26
27#include <clang/Basic/Version.h>
28#include <clang/Frontend/CompilerInstance.h>
29#include <clang/Tooling/CompilationDatabase.h>
30#include <clang/Tooling/Tooling.h>
31#include <glob/glob.hpp>
32#include <inja/inja.hpp>
33
35
42
43std::string to_plantuml(const relationship &r, const config::diagram &cfg);
44std::string to_plantuml(access_t scope);
45std::string to_plantuml(message_t r);
46
47/**
48 * @brief Base class for diagram generators
49 *
50 * @tparam ConfigType Configuration type
51 * @tparam DiagramType Diagram model type
52 */
53template <typename ConfigType, typename DiagramType>
55 : public clanguml::common::generators::generator<ConfigType, DiagramType> {
56public:
57 /**
58 * @brief Constructor
59 *
60 * @param config Reference to instance of @link clanguml::config::diagram
61 * @param model Reference to instance of @link clanguml::model::diagram
62 */
63 generator(ConfigType &config, DiagramType &model)
64 : clanguml::common::generators::generator<ConfigType, DiagramType>{
66 {
67 }
68
69 ~generator() override = default;
70
71 /**
72 * @brief Generate diagram
73 *
74 * This is the main diagram generation entrypoint. It is responsible for
75 * calling other methods in appropriate order to generate the diagram into
76 * the output stream. It generates diagram elements, that are common
77 * to all types of diagrams in a given generator.
78 *
79 * @param ostr Output stream
80 */
81 void generate(std::ostream &ostr) const override;
82
83 /**
84 * @brief Generate diagram specific part
85 *
86 * This method must be implemented in subclasses for specific diagram
87 * types.
88 *
89 * @param ostr Output stream
90 */
91 virtual void generate_diagram(std::ostream &ostr) const = 0;
92
93 /**
94 * @brief Generate diagram layout hints
95 *
96 * This method adds to the diagram any layout hints that were provided
97 * in the configuration file.
98 *
99 * @param ostr Output stream
100 */
101 void generate_config_layout_hints(std::ostream &ostr) const;
102
103 /**
104 * @brief Generate PlantUML directives from config file.
105 *
106 * This method renders the PlantUML directives provided in the configuration
107 * file, including resolving any element aliases and Jinja templates.
108 *
109 * @param ostr Output stream
110 * @param directives List of directives from the configuration file
111 */
113 std::ostream &ostr, const std::vector<std::string> &directives) const;
114
115 /**
116 * @brief Generate diagram notes
117 *
118 * This method adds any notes in the diagram, which were declared in the
119 * code using inline directives
120 *
121 * @param ostr Output stream
122 * @param element Element to which the note should be attached
123 */
124 void generate_notes(
125 std::ostream &ostr, const model::element &element) const;
126
127 /**
128 * @brief Generate diagram element PlantUML style
129 *
130 * This method renders a style for a specific `el` element if specified
131 * in the config file or inline comment directive.
132 *
133 * @param ostr Output stream
134 * @param element_type Name of the element type (e.g. "class")
135 * @param el Reference to a stylable diagram element
136 */
137 void generate_style(std::ostream &ostr, const std::string &element_type,
138 const model::stylable_element &el) const;
139
140 /**
141 * @brief Generate comment with diagram metadata
142 *
143 * @param ostr Output stream
144 */
145 void generate_metadata(std::ostream &ostr) const;
146
147 /**
148 * @brief Generate diagram title
149 *
150 * Generates a PlantUML diagram title directive if diagram title
151 * is provided in the diagram configuration.
152 *
153 * @param ostr Output stream
154 */
155 void generate_title(std::ostream &ostr) const;
156
157 /**
158 * @brief Generate hyper link to element
159 *
160 * This method renders links to URL's based on templates provided
161 * in the configuration file (e.g. Git browser with specific line and
162 * column offset)
163 *
164 * @param ostr Output stream
165 * @param e Reference to diagram element
166 * @tparam E Diagram element type
167 */
168 template <typename E>
169 void generate_link(std::ostream &ostr, const E &e) const;
170
171 /**
172 * @brief generate_link specialization for relationship
173 *
174 * @param ostr Output stream
175 * @param e Reference to diagram relationship
176 */
177 void generate_link(std::ostream &ostr, const relationship &e) const;
178
179 /**
180 * @brief Print debug information in diagram comments
181 *
182 * @param m Diagram element to describe
183 * @param ostr Output stream
184 */
185 void print_debug(
186 const common::model::source_location &e, std::ostream &ostr) const;
187
188private:
189 void generate_row_column_hints(std::ostream &ostr,
190 const std::string &entity_name, const config::layout_hint &hint) const;
191
192 void generate_position_hints(std::ostream &ostr,
193 const std::string &entity_name, const config::layout_hint &hint) const;
194
195protected:
196 mutable std::set<std::string> m_generated_aliases;
197};
198
199template <typename C, typename D>
200void generator<C, D>::generate(std::ostream &ostr) const
201{
202 const auto &config = generators::generator<C, D>::config();
203 const auto &model = generators::generator<C, D>::model();
204
206
207 if (!config.allow_empty_diagrams() && model.is_empty() &&
208 config.puml().before.empty() && config.puml().after.empty()) {
209 throw clanguml::error::empty_diagram_error{model.type(), model.name(),
210 "Diagram configuration resulted in empty diagram."};
211 }
212
213 ostr << "@startuml" << '\n';
214
215 generate_title(ostr);
216
217 // Generate PlantUML directives before auto generated content
218 generate_plantuml_directives(ostr, config.puml().before);
219
220 generate_diagram(ostr);
221
222 // Generate PlantUML directives after auto generated content
223 generate_plantuml_directives(ostr, config.puml().after);
224
225 generate_metadata(ostr);
226
227 ostr << "@enduml" << '\n';
228}
229
230template <typename C, typename D>
232{
233 using namespace clanguml::util;
234
235 const auto &config = generators::generator<C, D>::config();
236
237 // Generate layout hints
238 for (const auto &[entity_name, hints] : config.layout()) {
239 for (const auto &hint : hints) {
240 try {
241 if (hint.hint == config::hint_t::together) {
242 // 'together' layout hint is handled separately
243 }
244 else if (hint.hint == config::hint_t::row ||
245 hint.hint == config::hint_t::column) {
246 generate_row_column_hints(ostr, entity_name, hint);
247 }
248 else {
249 generate_position_hints(ostr, entity_name, hint);
250 }
251 }
253 LOG_DBG("=== Skipping layout hint '{}' from {} due "
254 "to: {}",
255 to_string(hint.hint), entity_name, e.what());
256 }
257 }
258 }
259}
260
261template <typename C, typename D>
263 const std::string &entity_name, const config::layout_hint &hint) const
264{
265 const auto &config = generators::generator<C, D>::config();
266 const auto &model = generators::generator<C, D>::model();
267
268 const auto &uns = config.using_namespace();
269
270 std::vector<std::string> group_elements;
271 std::vector<std::pair<std::string, std::string>> element_aliases_pairs;
272
273 group_elements.push_back(entity_name);
274 const auto &group_tail = std::get<std::vector<std::string>>(hint.entity);
275 std::copy(group_tail.begin(), group_tail.end(),
276 std::back_inserter(group_elements));
277
278 auto element_opt = model.get(entity_name);
279 if (!element_opt)
280 element_opt = model.get((uns | entity_name).to_string());
281
282 for (auto it = cbegin(group_elements);
283 it != cend(group_elements) && std::next(it) != cend(group_elements);
284 ++it) {
285 const auto &first = *it;
286 const auto &second = *std::next(it);
287
288 auto first_opt = model.get(first);
289 if (!first_opt)
290 first_opt = model.get((uns | first).to_string());
291
292 auto second_opt = model.get(second);
293 if (!second_opt)
294 second_opt = model.get((uns | second).to_string());
295
296 element_aliases_pairs.emplace_back(
297 first_opt.value().alias(), second_opt.value().alias());
298 }
299
300 std::string hint_direction =
301 hint.hint == clanguml::config::hint_t::row ? "right" : "down";
302
303 for (const auto &[f, s] : element_aliases_pairs) {
304 ostr << f << " -[hidden]" << hint_direction << "- " << s << '\n';
305 }
306}
307
308template <typename C, typename D>
310 const std::string &entity_name, const config::layout_hint &hint) const
311{
312 const auto &config = generators::generator<C, D>::config();
313 const auto &model = generators::generator<C, D>::model();
314
315 const auto &uns = config.using_namespace();
316
317 const auto &hint_entity = std::get<std::string>(hint.entity);
318
319 auto element_opt = model.get(entity_name);
320 if (!element_opt)
321 element_opt = model.get((uns | entity_name).to_string());
322
323 auto hint_element_opt = model.get(hint_entity);
324 if (!hint_element_opt)
325 hint_element_opt = model.get((uns | hint_entity).to_string());
326
327 if (!element_opt || !hint_element_opt)
328 return;
329
330 std::stringstream hint_str;
331
332 hint_str << element_opt.value().alias() << " -[hidden]"
333 << clanguml::config::to_string(hint.hint) << "- "
334 << hint_element_opt.value().alias() << '\n';
335
336 ostr << hint_str.str();
337}
338
339template <typename C, typename D>
341 std::ostream &ostr, const std::vector<std::string> &directives) const
342{
344
345 for (const auto &d : directives) {
346 auto rendered_directive =
349
350 if (rendered_directive)
351 ostr << *rendered_directive << '\n';
352 }
353}
354
355template <typename C, typename D>
356void generator<C, D>::generate_style(std::ostream &ostr,
357 const std::string &element_type, const model::stylable_element &el) const
358{
359 const auto &config = generators::generator<C, D>::config();
360
361 if (el.style() && !el.style().value().empty()) // NOLINT
362 ostr << " " << *el.style(); // NOLINT
363 else if (config.puml) {
364 if (const auto config_style = config.puml().get_style(element_type);
365 config_style) {
366 ostr << " " << *config_style;
367 }
368 }
369}
370
371template <typename C, typename D>
373 std::ostream &ostr, const model::element &e) const
374{
375 const auto &config = generators::generator<C, D>::config();
376
377 for (const auto &decorator : e.decorators()) {
378 auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
379 if (note && note->applies_to_diagram(config.name)) {
380 ostr << "note " << note->position << " of " << e.alias() << '\n'
381 << note->text << '\n'
382 << "end note\n";
383 }
384 }
385}
386
387template <typename C, typename D>
388void generator<C, D>::generate_metadata(std::ostream &ostr) const
389{
390 const auto &config = generators::generator<C, D>::config();
391
392 if (config.generate_metadata()) {
393 ostr << '\n'
394 << "'Generated with clang-uml, version "
395 << clanguml::version::version() << '\n'
396 << "'LLVM version " << clang::getClangFullVersion() << '\n';
397 }
398}
399
400template <typename C, typename D>
401void generator<C, D>::generate_title(std::ostream &ostr) const
402{
403 const auto &config = generators::generator<C, D>::config();
404
405 if (config.title) {
406 ostr << "title " << config.title() << '\n';
407 }
408}
409
410template <typename C, typename D>
411template <typename E>
412void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
413{
414 const auto maybe_link = generator<C, D>::render_link(e);
415 const auto maybe_tooltip = generator<C, D>::render_tooltip(e);
416
417 if (!maybe_link && !maybe_tooltip)
418 return;
419
420 ostr << " [[";
421
422 ostr << maybe_link.value_or("");
423
424 if (maybe_tooltip)
425 ostr << "{" << *maybe_tooltip << "}";
426
427 ostr << "]]";
428}
429
430template <typename C, typename D>
432 std::ostream &ostr, const common::model::relationship &r) const
433{
434 const auto maybe_link = generator<C, D>::render_link(r);
435 const auto maybe_tooltip = generator<C, D>::render_tooltip(r);
436
437 if (!maybe_link && !maybe_tooltip)
438 return;
439
440 ostr << " [[";
441
442 ostr << maybe_link.value_or("");
443
444 if (maybe_tooltip)
445 ostr << "{" << *maybe_tooltip << "}";
446
447 ostr << "]]";
448}
449
450template <typename C, typename D>
452 const common::model::source_location &e, std::ostream &ostr) const
453{
454 const auto &config = generators::generator<C, D>::config();
455
456 if (config.debug_mode()) {
457 if (!e.file_relative().empty()) {
458 ostr << "' " << e.file_relative() << ":" << e.line() << '\n';
459 }
460 else if (!e.file().empty()) {
461 ostr << "' " << e.file() << ":" << e.line() << '\n';
462 }
463 }
464}
465
466template <typename DiagramModel, typename DiagramConfig>
467std::ostream &operator<<(
468 std::ostream &os, const generator<DiagramModel, DiagramConfig> &g)
469{
470 g.generate(os);
471 return os;
472}
473
474} // namespace clanguml::common::generators::plantuml