0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
generators.cc
Go to the documentation of this file.
1/**
2 * @file src/common/generators/generators.cc
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
19#include "generators.h"
20
21#include "progress_indicator.h"
22
25 inja::json &context, const std::string &prefix)
26{
27 if (!context.contains("element"))
28 return;
29
30 if (!context["element"].contains("source"))
31 return;
32
33 auto &source = context["element"]["source"];
34
35 if (source.at("path").empty())
36 return;
37
38 auto path = std::filesystem::path{source.at("path")};
39 auto prefix_path = std::filesystem::path(prefix);
40 if (path.is_absolute() && util::is_relative_to(path, prefix_path)) {
41 source["path"] = relative(path, prefix_path);
42 return;
43 }
44}
45
47 const std::vector<std::string> &diagram_names,
49 const std::vector<std::string> &compilation_database_files,
50 std::map<std::string, std::vector<std::string>> &translation_units_map)
51{
52 for (const auto &[name, diagram] : config.diagrams) {
53 // If there are any specific diagram names provided on the command line,
54 // and this diagram is not in that list - skip it
55 if (!diagram_names.empty() && !util::contains(diagram_names, name))
56 continue;
57
58 translation_units_map[name] =
59 diagram->glob_translation_units(compilation_database_files);
60
61 LOG_DBG("Found {} translation units for diagram {}",
62 translation_units_map.at(name).size(), name);
63 }
64}
65
67 std::shared_ptr<config::diagram> diagram_config)
68{
69 std::string cmd;
70 switch (generator_type) {
72 cmd = diagram_config->puml().cmd;
73 break;
75 cmd = diagram_config->mermaid().cmd;
76 break;
77 default:
78 return;
79 };
80
81 if (cmd.empty())
82 throw std::runtime_error(
83 fmt::format("No render command template provided for {} diagrams",
84 to_string(diagram_config->type())));
85
86 util::replace_all(cmd, "{}", diagram_config->name);
87
88 LOG_INFO("Rendering diagram {} using {}", diagram_config->name,
89 to_string(generator_type));
90
92}
93
94namespace detail {
95
96template <typename DiagramConfig, typename GeneratorTag, typename DiagramModel>
97void generate_diagram_select_generator(const std::string &od,
98 const std::string &name, std::shared_ptr<clanguml::config::diagram> diagram,
99 const DiagramModel &model)
100{
101 using diagram_generator =
103
104 std::stringstream buffer;
105 buffer << diagram_generator(
106 dynamic_cast<DiagramConfig &>(*diagram), *model);
107
108 // Only open the file after the diagram has been generated successfully
109 // in order not to overwrite previous diagram in case of failure
110 auto path = std::filesystem::path{od} /
111 fmt::format("{}.{}", name, GeneratorTag::extension);
112 std::ofstream ofs;
113 ofs.open(path, std::ofstream::out | std::ofstream::trunc);
114 ofs << buffer.str();
115
116 ofs.close();
117
118 LOG_INFO("Written {} diagram to {}", name, path.string());
119}
120
121template <typename DiagramConfig>
122void generate_diagram_impl(const std::string &name,
123 std::shared_ptr<clanguml::config::diagram> diagram,
125 const std::vector<std::string> &translation_units,
126 const cli::runtime_config &runtime_config, std::function<void()> &&progress)
127{
128 using diagram_config = DiagramConfig;
129 using diagram_model = typename diagram_model_t<DiagramConfig>::type;
130 using diagram_visitor = typename diagram_visitor_t<DiagramConfig>::type;
131
132 auto model = clanguml::common::generators::generate<diagram_model,
133 diagram_config, diagram_visitor>(db, diagram->name,
134 dynamic_cast<diagram_config &>(*diagram), translation_units,
135 runtime_config.verbose, std::move(progress));
136
137 if constexpr (std::is_same_v<DiagramConfig, config::sequence_diagram>) {
138 if (runtime_config.print_from) {
139 auto from_values = model->list_from_values();
140
141 for (const auto &from : from_values) {
142 std::cout << from << '\n';
143 }
144
145 return;
146 }
147 if (runtime_config.print_to) {
148 auto to_values = model->list_to_values();
149
150 for (const auto &to : to_values) {
151 std::cout << "|" << to << "|" << '\n';
152 }
153
154 return;
155 }
156 }
157
158 for (const auto generator_type : runtime_config.generators) {
159 if (generator_type == generator_type_t::plantuml) {
162 runtime_config.output_directory, name, diagram, model);
163 }
164 else if (generator_type == generator_type_t::json) {
167 runtime_config.output_directory, name, diagram, model);
168 }
169 else if (generator_type == generator_type_t::mermaid) {
172 runtime_config.output_directory, name, diagram, model);
173 }
174
175 // Convert plantuml or mermaid to an image using command provided
176 // in the command line arguments
177 if (runtime_config.render_diagrams) {
178 render_diagram(generator_type, diagram);
179 }
180 }
181}
182} // namespace detail
183
184void generate_diagram(const std::string &name,
185 std::shared_ptr<clanguml::config::diagram> diagram,
187 const std::vector<std::string> &translation_units,
188 const cli::runtime_config &runtime_config, std::function<void()> &&progress)
189{
192
197
198 if (diagram->type() == diagram_t::kClass) {
199 detail::generate_diagram_impl<class_diagram>(name, diagram, db,
200 translation_units, runtime_config, std::move(progress));
201 }
202 else if (diagram->type() == diagram_t::kSequence) {
203 detail::generate_diagram_impl<sequence_diagram>(name, diagram, db,
204 translation_units, runtime_config, std::move(progress));
205 }
206 else if (diagram->type() == diagram_t::kPackage) {
207 detail::generate_diagram_impl<package_diagram>(name, diagram, db,
208 translation_units, runtime_config, std::move(progress));
209 }
210 else if (diagram->type() == diagram_t::kInclude) {
211 detail::generate_diagram_impl<include_diagram>(name, diagram, db,
212 translation_units, runtime_config, std::move(progress));
213 }
214}
215
216void generate_diagrams(const std::vector<std::string> &diagram_names,
218 const cli::runtime_config &runtime_config,
219 const std::map<std::string, std::vector<std::string>>
220 &translation_units_map)
221{
222 util::thread_pool_executor generator_executor{runtime_config.thread_count};
223 std::vector<std::future<void>> futs;
224
225 std::unique_ptr<progress_indicator> indicator;
226
227 if (runtime_config.progress) {
228 std::cout << termcolor::white
229 << "Processing translation units and generating diagrams:\n";
230 indicator = std::make_unique<progress_indicator>();
231 }
232
233 for (const auto &[name, diagram] : config.diagrams) {
234 // If there are any specific diagram names provided on the command
235 // line, and this diagram is not in that list - skip it
236 if (!diagram_names.empty() && !util::contains(diagram_names, name))
237 continue;
238
239 const auto &valid_translation_units = translation_units_map.at(name);
240
241 LOG_DBG("Found {} valid translation units for diagram {}",
242 valid_translation_units.size(), name);
243
244 const auto matching_commands_count =
245 db->count_matching_commands(valid_translation_units);
246
247 if (matching_commands_count == 0) {
248 if (indicator) {
249 indicator->add_progress_bar(
250 name, 0, diagram_type_to_color(diagram->type()));
251 indicator->fail(name);
252 }
253 else {
254 LOG_ERROR(
255 "Diagram {} generation failed: no translation units "
256 "found. Please make sure that your 'glob' patterns match "
257 "at least 1 file in 'compile_commands.json'.",
258 name);
259 }
260 continue;
261 }
262
263 LOG_DBG("Found {} matching translation unit commands for diagram {}",
264 matching_commands_count, name);
265
266 auto generator = [&name = name, &diagram = diagram, &indicator,
267 db = std::ref(*db), matching_commands_count,
268 translation_units = valid_translation_units,
269 runtime_config]() mutable {
270 try {
271 if (indicator)
272 indicator->add_progress_bar(name, matching_commands_count,
273 diagram_type_to_color(diagram->type()));
274
275 generate_diagram(name, diagram, db, translation_units,
276 runtime_config, [&indicator, &name]() {
277 if (indicator)
278 indicator->increment(name);
279 });
280
281 if (indicator)
282 indicator->complete(name);
283 }
284 catch (const std::exception &e) {
285 if (indicator)
286 indicator->fail(name);
287
288 LOG_ERROR(
289 "ERROR: Failed to generate diagram {}: {}", name, e.what());
290 }
291 };
292
293 futs.emplace_back(generator_executor.add(std::move(generator)));
294 }
295
296 for (auto &fut : futs) {
297 fut.get();
298 }
299
300 if (runtime_config.progress) {
301 indicator->stop();
302 std::cout << termcolor::white << "Done\n";
303 std::cout << termcolor::reset;
304 }
305}
306
307indicators::Color diagram_type_to_color(model::diagram_t diagram_type)
308{
309 switch (diagram_type) {
311 return indicators::Color::yellow;
313 return indicators::Color::blue;
315 return indicators::Color::cyan;
317 return indicators::Color::magenta;
318 default:
319 return indicators::Color::unspecified;
320 }
321}
322
323} // namespace clanguml::common::generators