25 inja::json &context,
const std::string &prefix)
27 if (!context.contains(
"element"))
30 if (!context[
"element"].contains(
"source"))
33 auto &source = context[
"element"][
"source"];
35 if (source.at(
"path").empty())
38 auto path = std::filesystem::path{source.at(
"path").get<std::string>()};
39 auto prefix_path = std::filesystem::path(prefix);
41 source[
"path"] = relative(path, prefix_path);
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)
52 for (
const auto &[name, diagram] : config.
diagrams) {
55 if (!diagram_names.empty() && !
util::contains(diagram_names, name))
58 translation_units_map[name] =
59 diagram->glob_translation_units(compilation_database_files);
61 LOG_DBG(
"Found {} translation units for diagram '{}'",
62 translation_units_map.at(name).size(), name);
67 std::shared_ptr<config::diagram> diagram_config)
70 switch (generator_type) {
72 cmd = diagram_config->
puml().cmd;
75 cmd = diagram_config->
mermaid().cmd;
82 throw std::runtime_error(
83 fmt::format(
"No render command template provided for {} diagrams",
88 LOG_INFO(
"Rendering diagram {} using {}", diagram_config->
name,
96template <
typename DiagramConfig,
typename GeneratorTag,
typename DiagramModel>
98 const std::string &name, std::shared_ptr<clanguml::config::diagram> diagram,
99 const DiagramModel &model)
101 using diagram_generator =
104 if constexpr (!std::is_same_v<diagram_generator, not_supported>) {
106 std::stringstream buffer;
107 buffer << diagram_generator(
108 dynamic_cast<DiagramConfig &
>(*diagram), *model);
112 auto path = std::filesystem::path{od} /
113 fmt::format(
"{}.{}", name, GeneratorTag::extension);
115 ofs.open(path, std::ofstream::out | std::ofstream::trunc);
120 LOG_INFO(
"Written {} diagram to {}", name, path.string());
123 LOG_INFO(
"Serialization to {} not supported for {}",
124 GeneratorTag::extension, name);
128template <
typename DiagramConfig>
130 std::shared_ptr<clanguml::config::diagram> diagram,
132 const std::vector<std::string> &translation_units,
135 using diagram_config = DiagramConfig;
140 diagram_config, diagram_visitor>(db, diagram->name,
141 dynamic_cast<diagram_config &
>(*diagram), translation_units,
142 runtime_config.
verbose, std::move(progress));
144 if constexpr (std::is_same_v<DiagramConfig, config::sequence_diagram>) {
146 auto from_values = model->list_from_values();
149 for (
const auto &from : from_values) {
150 std::cout << from <<
'\n';
154 inja::json j = inja::json::array();
155 for (
const auto &from : from_values) {
156 j.emplace_back(logging::escape_json(from));
158 std::cout << j.dump();
164 auto to_values = model->list_to_values();
166 for (
const auto &to : to_values) {
167 std::cout << to <<
'\n';
171 inja::json j = inja::json::array();
172 for (
const auto &to : to_values) {
173 j.emplace_back(logging::escape_json(to));
175 std::cout << j.dump();
181 for (
const auto generator_type : runtime_config.
generators) {
213 std::shared_ptr<clanguml::config::diagram> diagram,
215 const std::vector<std::string> &translation_units,
226 if (diagram->type() == diagram_t::kClass) {
227 detail::generate_diagram_impl<class_diagram>(name, diagram, db,
228 translation_units, runtime_config, std::move(progress));
230 else if (diagram->type() == diagram_t::kSequence) {
231 detail::generate_diagram_impl<sequence_diagram>(name, diagram, db,
232 translation_units, runtime_config, std::move(progress));
234 else if (diagram->type() == diagram_t::kPackage) {
235 detail::generate_diagram_impl<package_diagram>(name, diagram, db,
236 translation_units, runtime_config, std::move(progress));
238 else if (diagram->type() == diagram_t::kInclude) {
239 detail::generate_diagram_impl<include_diagram>(name, diagram, db,
240 translation_units, runtime_config, std::move(progress));
247 const std::map<std::string, std::vector<std::string>>
248 &translation_units_map)
251 std::vector<std::future<void>> futs;
253 std::unique_ptr<progress_indicator_base> indicator;
259 <<
"Processing translation units and generating diagrams:\n";
260 indicator = std::make_unique<progress_indicator>();
263 indicator = std::make_unique<json_logger_progress_indicator>();
267 std::vector<std::exception_ptr> errors;
269 for (
const auto &[name, diagram] : config.
diagrams) {
272 if (!diagram_names.empty() && !
util::contains(diagram_names, name))
276 bool at_least_one_generator_supports_diagram_type{
false};
277 for (
const auto generator_type : runtime_config.
generators) {
279 if (generator_supports_diagram_type<plantuml_generator_tag>(
281 at_least_one_generator_supports_diagram_type =
true;
284 if (generator_supports_diagram_type<json_generator_tag>(
286 at_least_one_generator_supports_diagram_type =
true;
289 if (generator_supports_diagram_type<mermaid_generator_tag>(
291 at_least_one_generator_supports_diagram_type =
true;
294 if (generator_supports_diagram_type<graphml_generator_tag>(
296 at_least_one_generator_supports_diagram_type =
true;
300 if (!at_least_one_generator_supports_diagram_type) {
301 LOG_INFO(
"Diagram '{}' not supported by any of selected "
302 "generators - skipping...",
307 const auto &valid_translation_units = translation_units_map.at(name);
309 LOG_DBG(
"Found {} possible translation units for diagram '{}'",
310 valid_translation_units.size(), name);
312 const auto matching_commands_count =
313 db->count_matching_commands(valid_translation_units);
315 if (matching_commands_count == 0) {
316 const auto error_msg = fmt::format(
317 "Diagram '{}' generation failed: no translation units "
318 "found. Please make sure that your 'glob' patterns match "
319 "at least 1 file in 'compile_commands.json'.",
323 indicator->add_progress_bar(
325 indicator->fail(name);
327 throw std::runtime_error(error_msg);
329 catch (std::runtime_error &e) {
330 errors.emplace_back(std::current_exception());
340 LOG_DBG(
"Found {} matching translation unit commands for diagram {}",
341 matching_commands_count, name);
343 auto generator = [&name = name, &diagram = diagram, &indicator,
344 db = std::ref(*db), matching_commands_count,
345 translation_units = valid_translation_units,
346 runtime_config]()
mutable ->
void {
349 indicator->add_progress_bar(name, matching_commands_count,
353 runtime_config, [&indicator, &name]() {
355 indicator->increment(name);
359 indicator->complete(name);
368 indicator->fail(name);
371 catch (std::exception &e) {
373 indicator->fail(name);
376 "Failed to generate diagram '{}': {}", name, e.
what());
378 throw std::runtime_error(fmt::format(
379 "Failed to generate diagram '{}': {}", name, e.
what()));
383 futs.emplace_back(generator_executor.add(std::move(
generator)));
386 for (
auto &fut : futs) {
390 catch (std::exception &e) {
391 errors.emplace_back(std::current_exception());
398 if (errors.empty()) {
399 std::cout << termcolor::white <<
"Done\n";
402 std::cout << termcolor::white <<
"\n";
404 std::cout << termcolor::reset;
410 for (
auto &e : errors) {
412 std::rethrow_exception(e);
416 logging::logger_type_t::text) {
418 fmt::println(
"ERROR: Failed to generate {} diagram '{}' due to "
422 fmt::println(
" - {}", d);
429 j[
"clang_errors"] = inja::json::array();
431 j[
"clang_errors"].emplace_back(d);
434 spdlog::get(
"clanguml-logger")
435 ->log(spdlog::level::err,
437 R
"("file": "{}", "line": {}, "message": {})"),
441 catch (
const std::exception &e) {
443 logging::logger_type_t::text) {
444 fmt::println(
"ERROR: {}", e.
what());
457 switch (diagram_type) {
459 return indicators::Color::yellow;
461 return indicators::Color::blue;
463 return indicators::Color::cyan;
465 return indicators::Color::magenta;
467 return indicators::Color::unspecified;