0.5.4
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/mermaid/generator.h
3 *
4 * Copyright (c) 2021-2024 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
22#include "config/config.h"
23#include "util/error.h"
24#include "util/util.h"
25#include "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
40
41std::string to_mermaid(relationship_t r);
42std::string to_mermaid(access_t scope);
43std::string to_mermaid(message_t r);
44
45std::string indent(unsigned level);
46
47std::string render_name(std::string name);
48std::string escape_name(std::string name, bool round_brackets = true);
49
50/**
51 * @brief Base class for diagram generators
52 *
53 * @tparam ConfigType Configuration type
54 * @tparam DiagramType Diagram model type
55 */
56template <typename ConfigType, typename DiagramType>
58 : public clanguml::common::generators::generator<ConfigType, DiagramType> {
59public:
60 /**
61 * @brief Constructor
62 *
63 * @param config Reference to instance of @link clanguml::config::diagram
64 * @param model Reference to instance of @link clanguml::model::diagram
65 */
66 generator(ConfigType &config, DiagramType &model)
67 : clanguml::common::generators::generator<ConfigType, DiagramType>{
69 {
70 }
71
72 ~generator() override = default;
73
74 /**
75 * @brief Generate diagram
76 *
77 * This is the main diagram generation entrypoint. It is responsible for
78 * calling other methods in appropriate order to generate the diagram into
79 * the output stream. It generates diagram elements, that are common
80 * to all types of diagrams in a given generator.
81 *
82 * @param ostr Output stream
83 */
84 void generate(std::ostream &ostr) const override;
85
86 /**
87 * @brief Generate diagram specific part
88 *
89 * This method must be implemented in subclasses for specific diagram
90 * types.
91 *
92 * @param ostr Output stream
93 */
94 virtual void generate_diagram(std::ostream &ostr) const = 0;
95
96 /**
97 * @brief Generate MermaidJS directives from config file.
98 *
99 * This method renders the MermaidJS directives provided in the
100 * configuration file, including resolving any element aliases and Jinja
101 * templates.
102 *
103 * @param ostr Output stream
104 * @param directives List of directives from the configuration file
105 */
107 std::ostream &ostr, const std::vector<std::string> &directives) const;
108
109 /**
110 * @brief Generate the diagram type
111 *
112 * This method must be overriden for each diagram type (e.g. it renders
113 * a single line `classDiagram` for Mermaid class diagrams.
114 *
115 * @param ostr Output stream
116 */
117 virtual void generate_diagram_type(std::ostream &ostr) const = 0;
118
119 /**
120 * @brief Generate diagram notes
121 *
122 * This method adds any notes in the diagram, which were declared in the
123 * code using inline directives
124 *
125 * @param ostr Output stream
126 * @param element Element to which the note should be attached
127 */
128 virtual void generate_notes(
129 std::ostream &ostr, const model::diagram_element &element) const;
130
131 /**
132 * @brief Generate comment with diagram metadata
133 *
134 * @param ostr Output stream
135 */
136 void generate_metadata(std::ostream &ostr) const;
137
138 /**
139 * @brief Generate diagram title
140 *
141 * Generates a MermaidJS diagram title directive if diagram title
142 * is provided in the diagram configuration.
143 *
144 * @param ostr Output stream
145 */
146 void generate_title(std::ostream &ostr) const;
147
148 /**
149 * @brief Generate hyper link to element
150 *
151 * This method renders links to URL's based on templates provided
152 * in the configuration file (e.g. Git browser with specific line and
153 * column offset)
154 *
155 * @param ostr Output stream
156 * @param e Reference to diagram element
157 * @tparam E Diagram element type
158 */
159 template <typename E>
160 void generate_link(std::ostream &ostr, const E &e) const;
161
162 /**
163 * @brief Print debug information in diagram comments
164 *
165 * @param m Diagram element to describe
166 * @param ostr Output stream
167 */
168 void print_debug(
169 const common::model::source_location &e, std::ostream &ostr) const;
170
171protected:
172 mutable std::set<std::string> m_generated_aliases;
173};
174
175template <typename C, typename D>
176void generator<C, D>::generate(std::ostream &ostr) const
177{
178 const auto &config = generators::generator<C, D>::config();
179 const auto &model = generators::generator<C, D>::model();
180
181 if (!config.allow_empty_diagrams() && model.is_empty() &&
182 config.mermaid().before.empty() && config.mermaid().after.empty()) {
184 "Diagram configuration resulted in empty diagram."};
185 }
186
188
189 generate_title(ostr);
190
191 generate_diagram_type(ostr);
192
193 generate_mermaid_directives(ostr, config.mermaid().before);
194
195 generate_diagram(ostr);
196
197 generate_mermaid_directives(ostr, config.mermaid().after);
198
199 generate_metadata(ostr);
200}
201
202template <typename C, typename D>
203template <typename E>
204void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
205{
206 if (e.file().empty() && e.file_relative().empty())
207 return;
208
209 auto maybe_link_pattern = generators::generator<C, D>::get_link_pattern(e);
210
211 if (!maybe_link_pattern)
212 return;
213
214 const auto &[link_prefix, link_pattern] = *maybe_link_pattern;
215
216 ostr << indent(1) << "click " << e.alias() << " href \"";
217 try {
220 std::string link{};
221 if (!link_pattern.empty()) {
222 link = generators::generator<C, D>::env().render(
223 std::string_view{link_pattern}, ec);
224 }
225 if (link.empty())
226 link = " ";
227 ostr << link;
228 }
229 catch (const inja::json::parse_error &e) {
230 LOG_ERROR("Failed to parse Jinja template: {}", link_pattern);
231 ostr << " ";
232 }
233 catch (const inja::json::exception &e) {
234 LOG_ERROR("Failed to render comment directive: \n{}\n due to: {}",
235 link_pattern, e.what());
236 ostr << " ";
237 }
238 ostr << "\"";
239
240 auto maybe_tooltip_pattern =
242
243 if (maybe_tooltip_pattern) {
244 const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
245
246 if (!tooltip_pattern.empty()) {
247 ostr << " \"";
248 try {
251 ec, tooltip_prefix);
252 auto tooltip_text = generators::generator<C, D>::env().render(
253 std::string_view{tooltip_pattern}, ec);
254 util::replace_all(tooltip_text, "\"", "&bdquo;");
255 ostr << tooltip_text;
256 }
257 catch (const inja::json::parse_error &e) {
258 LOG_ERROR(
259 "Failed to parse Jinja template: {}", tooltip_pattern);
260 ostr << " ";
261 }
262 catch (const inja::json::exception &e) {
263 LOG_ERROR(
264 "Failed to render PlantUML directive: \n{}\n due to: {}",
265 tooltip_pattern, e.what());
266 ostr << " ";
267 }
268
269 ostr << "\"";
270 }
271 }
272 ostr << "\n";
273}
274
275template <typename C, typename D>
277 std::ostream &ostr, const std::vector<std::string> &directives) const
278{
279
280 const auto &config = generators::generator<C, D>::config();
281 const auto &model = generators::generator<C, D>::model();
282
284
285 for (const auto &d : directives) {
286 try {
287 // Render the directive with template engine first
288 std::string directive{generators::generator<C, D>::env().render(
289 std::string_view{d}, generators::generator<C, D>::context())};
290
291 // Now search for alias `@A()` directives in the text
292 // (this is deprecated)
293 std::tuple<std::string, size_t, size_t> alias_match;
294 while (util::find_element_alias(directive, alias_match)) {
295 const auto full_name =
296 config.using_namespace() | std::get<0>(alias_match);
297 auto element_opt = model.get(full_name.to_string());
298
299 if (element_opt)
300 directive.replace(std::get<1>(alias_match),
301 std::get<2>(alias_match), element_opt.value().alias());
302 else {
303 LOG_ERROR("Cannot find clang-uml alias for element {}",
304 full_name.to_string());
305 directive.replace(std::get<1>(alias_match),
306 std::get<2>(alias_match), "UNKNOWN_ALIAS");
307 }
308 }
309
310 ostr << indent(1) << directive << '\n';
311 }
312 catch (const clanguml::error::uml_alias_missing &e) {
313 LOG_ERROR(
314 "Failed to render MermaidJS directive due to unresolvable "
315 "alias: {}",
316 e.what());
317 }
318 catch (const inja::json::parse_error &e) {
319 LOG_ERROR("Failed to parse Jinja template: {}", d);
320 }
321 catch (const inja::json::exception &e) {
322 LOG_ERROR("Failed to render MermaidJS directive: \n{}\n due to: {}",
323 d, e.what());
324 }
325 catch (const std::regex_error &e) {
326 LOG_ERROR("Failed to render MermaidJS directive: \n{}\n due to "
327 "std::regex_error: {}",
328 d, e.what());
329 }
330 catch (const std::exception &e) {
331 LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
332 d, e.what());
333 }
334 }
335}
336
337template <typename C, typename D>
339 std::ostream &ostr, const model::diagram_element &e) const
340{
341 const auto &config = generators::generator<C, D>::config();
342
343 for (const auto &decorator : e.decorators()) {
344 auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
345 if (note && note->applies_to_diagram(config.name)) {
346 ostr << indent(1) << "note for " << e.alias() << " \"" << note->text
347 << "\"" << '\n';
348 }
349 }
350}
351
352template <typename C, typename D>
353void generator<C, D>::generate_metadata(std::ostream &ostr) const
354{
355 const auto &config = generators::generator<C, D>::config();
356
357 if (config.generate_metadata()) {
358 ostr << '\n'
359 << "%% Generated with clang-uml, version "
360 << clanguml::version::CLANG_UML_VERSION << '\n'
361 << "%% LLVM version " << clang::getClangFullVersion() << '\n';
362 }
363}
364
365template <typename C, typename D>
366void generator<C, D>::generate_title(std::ostream &ostr) const
367{
368 const auto &config = generators::generator<C, D>::config();
369
370 if (config.title) {
371 ostr << "---\n";
372 ostr << "title: " << config.title() << '\n';
373 ostr << "---\n";
374 }
375}
376
377template <typename C, typename D>
379 const common::model::source_location &e, std::ostream &ostr) const
380{
381 const auto &config = generators::generator<C, D>::config();
382
383 if (config.debug_mode())
384 ostr << "%% " << e.file() << ":" << e.line() << '\n';
385}
386
387template <typename DiagramModel, typename DiagramConfig>
388std::ostream &operator<<(
389 std::ostream &os, const generator<DiagramModel, DiagramConfig> &g)
390{
391 g.generate(os);
392 return os;
393}
394} // namespace clanguml::common::generators::mermaid