27#include <clang/Basic/Version.h>
28#include <clang/Config/config.h>
29#include <indicators/indicators.hpp>
33 std::ostream &ostr, std::shared_ptr<spdlog::logger> logger)
35 , logger_{
std::move(logger)}
41 spdlog::drop(
"clanguml-logger");
43 spdlog::register_logger(
logger_);
45 logger_->set_pattern(
"[%^%l%^] [tid %t] %v");
48 logger_->set_level(spdlog::level::err);
51 logger_->set_level(spdlog::level::info);
54 logger_->set_level(spdlog::level::debug);
57 logger_->set_level(spdlog::level::trace);
63 static const std::map<std::string, clanguml::common::generator_type_t>
70 "Location of configuration file, when '-' read from stdin");
72 "Location of compilation database directory");
74 "List of diagram names to generate");
76 "Name of the generator (default: plantuml)")
77 ->transform(CLI::CheckedTransformer(generator_type_names));
79 "Override output directory specified in config file");
81 "Thread pool size (0 = hardware concurrency)");
84 "Verbose logging (use multiple times to increase - e.g. -vvv)");
86 "-p,--progress",
progress,
"Show progress bars for generated diagrams");
87 app.add_flag(
"-q,--quiet",
quiet,
"Minimal logging");
89 "Print list of diagrams defined in the config file");
90 app.add_flag(
"--init",
initialize,
"Initialize example config file");
92 "Add a compilation flag to each entry in the compilation database");
94 "Remove a compilation flag from each entry in the compilation "
98 "Query the specific compiler driver to extract system paths and add to "
99 "compile commands (e.g. arm-none-eabi-g++)");
102 "Do not raise an error when generated diagram model is empty");
106 "Add sequence diagram config");
108 "Add package diagram config");
110 "Add include diagram config");
112 "Add diagram config based on diagram template");
114 "Specify a value for a template variable");
116 "List all available diagram templates");
118 "Show specific diagram template definition");
120 "--dump-config",
dump_config,
"Print effective config to stdout");
122 "If true, all paths in configuration files are relative to the $PWD "
123 "instead of actual location of `.clang-uml` file.");
125 "Skip metadata (e.g. clang-uml version) from diagrams");
127 "Print all possible 'from' values for a given diagram");
129 "Print all possible 'to' values for a given diagram");
131 "Do not perform configuration file schema validation");
133 "Perform configuration file schema validation and exit");
135 "Automatically render generated diagrams using appropriate command");
137 "Command template to render PlantUML diagram, `{}` will be replaced "
138 "with diagram name.");
140 "Command template to render MermaidJS diagram, `{}` will be replaced "
141 "with diagram name.");
144 app.parse(argc, argv);
146 catch (
const CLI::CallForHelp &e) {
149 catch (
const CLI::Success &e) {
152 catch (
const CLI::ParseError &e) {
169 auto res =
parse(argc, argv);
191 spdlog::drop(
"clanguml-logger");
194 std::vector<spdlog::sink_ptr> sinks;
195 logger_ = std::make_shared<spdlog::logger>(
196 "clanguml-logger", begin(sinks), end(sinks));
197 spdlog::register_logger(
logger_);
216 "ERROR: Cannot add a diagram config to configuration from stdin");
223 LOG_ERROR(
"ERROR: '--print-from' and '--print-to' require "
224 "specifying one diagram name using '-n' option");
269 std::cout <<
"Configuration file " <<
config_path <<
" is valid.\n";
276 catch (std::runtime_error &e) {
333 LOG_INFO(
"Loading compilation database from {} directory",
399 ostr_ <<
"clang-uml " << clanguml::version::CLANG_UML_VERSION <<
'\n';
400 ostr_ <<
"Copyright (C) 2021-2024 Bartek Kryza <bkryza@gmail.com>" <<
'\n';
402 ostr_ <<
"Built against LLVM/Clang libraries version: "
403 << LLVM_VERSION_STRING <<
'\n';
404 ostr_ <<
"Using LLVM/Clang libraries version: "
405 << clang::getClangFullVersion() <<
'\n';
412 namespace fs = std::filesystem;
415 fs::path output_dir{dir};
417 if (fs::exists(output_dir) && !fs::is_directory(output_dir)) {
418 cout <<
"ERROR: " << dir <<
" is not a directory...\n";
422 if (!fs::exists(output_dir)) {
423 return fs::create_directories(output_dir);
433 ostr_ <<
"The following diagrams are defined in the config file:\n";
435 ostr_ <<
" - " << name <<
" [" << to_string(diagram->type()) <<
"]";
447 ostr_ <<
"No diagram templates are defined in the config file\n";
451 ostr_ <<
"The following diagram templates are available:\n";
453 ostr_ <<
" - " << name <<
" [" << to_string(diagram_template.type)
455 if (!diagram_template.description.empty())
456 ostr_ <<
": " << diagram_template.description;
467 ostr_ <<
"No such diagram template: " << template_name <<
"\n";
472 if (template_name == name) {
473 ostr_ << diagram_template.jinja_template <<
"\n";
483 namespace fs = std::filesystem;
487 if (fs::exists(config_file)) {
488 ostr_ <<
"ERROR: .clang-uml file already exists\n";
494 out << YAML::BeginMap;
495 out << YAML::Comment(
"Change to directory where compile_commands.json is");
496 out << YAML::Key <<
"compilation_database_dir" << YAML::Value <<
".";
498 << YAML::Comment(
"Change to directory where diagram should be written");
499 out << YAML::Key <<
"output_directory" << YAML::Value <<
"docs/diagrams";
500 out << YAML::Key <<
"diagrams" << YAML::Value;
501 out << YAML::BeginMap;
502 out << YAML::Key <<
"example_class_diagram" << YAML::Value;
503 out << YAML::BeginMap;
504 out << YAML::Key <<
"type" << YAML::Value <<
"class";
505 out << YAML::Key <<
"glob" << YAML::Value;
506 out << YAML::BeginSeq <<
"src/*.cpp" << YAML::EndSeq;
507 out << YAML::Key <<
"using_namespace" << YAML::Value;
508 out << YAML::BeginSeq <<
"myproject" << YAML::EndSeq;
509 out << YAML::Key <<
"include";
510 out << YAML::BeginMap;
511 out << YAML::Key <<
"namespaces";
512 out << YAML::BeginSeq <<
"myproject" << YAML::EndSeq;
514 out << YAML::Key <<
"exclude";
515 out << YAML::BeginMap;
516 out << YAML::Key <<
"namespaces";
517 out << YAML::BeginSeq <<
"myproject::detail" << YAML::EndSeq;
522 out << YAML::Newline;
524 std::ofstream ofs(config_file);
533 const std::string &config_file_path,
const std::string &name)
535 namespace fs = std::filesystem;
537 fs::path config_file{config_file_path};
539 if (!fs::exists(config_file)) {
540 std::cerr <<
"ERROR: " << config_file_path <<
" file doesn't exists\n";
544 YAML::Node doc = YAML::LoadFile(config_file.string());
546 for (YAML::const_iterator it = doc[
"diagrams"].begin();
547 it != doc[
"diagrams"].end(); ++it) {
548 if (it->first.as<std::string>() == name) {
549 std::cerr <<
"ERROR: " << config_file_path
550 <<
" file already contains '" << name <<
"' diagram";
556 doc[
"diagrams"][name][
"type"] =
"class";
557 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
558 doc[
"diagrams"][name][
"using_namespace"] =
559 std::vector<std::string>{{
"myproject"}};
560 doc[
"diagrams"][name][
"include"][
"namespaces"] =
561 std::vector<std::string>{{
"myproject"}};
562 doc[
"diagrams"][name][
"exclude"][
"namespaces"] =
563 std::vector<std::string>{{
"myproject::detail"}};
566 doc[
"diagrams"][name][
"type"] =
"sequence";
567 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
568 doc[
"diagrams"][name][
"combine_free_functions_into_file_participants"] =
570 doc[
"diagrams"][name][
"inline_lambda_messages"] =
false;
571 doc[
"diagrams"][name][
"generate_message_comments"] =
false;
572 doc[
"diagrams"][name][
"generate_condition_statements"] =
false;
573 doc[
"diagrams"][name][
"using_namespace"] =
574 std::vector<std::string>{{
"myproject"}};
575 doc[
"diagrams"][name][
"include"][
"paths"] =
576 std::vector<std::string>{{
"src"}};
577 doc[
"diagrams"][name][
"exclude"][
"namespaces"] =
578 std::vector<std::string>{{
"myproject::detail"}};
579 doc[
"diagrams"][name][
"start_from"] =
580 std::vector<std::map<std::string, std::string>>{
581 {{
"function",
"main(int,const char **)"}}};
584 doc[
"diagrams"][name][
"type"] =
"package";
585 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
586 doc[
"diagrams"][name][
"using_namespace"] =
587 std::vector<std::string>{{
"myproject"}};
588 doc[
"diagrams"][name][
"include"][
"namespaces"] =
589 std::vector<std::string>{{
"myproject"}};
590 doc[
"diagrams"][name][
"exclude"][
"namespaces"] =
591 std::vector<std::string>{{
"myproject::detail"}};
594 doc[
"diagrams"][name][
"type"] =
"include";
595 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
596 doc[
"diagrams"][name][
"relative_to"] =
".";
597 doc[
"diagrams"][name][
"include"][
"paths"] =
598 std::vector<std::string>{{
"src"}};
605 out << YAML::Newline;
607 std::ofstream ofs(config_file);
615 const std::string &config_file_path,
const std::string &template_name)
620 std::cerr <<
"ERROR: No such diagram template: " << template_name
630 if (var.size() != 2) {
631 std::cerr <<
"ERROR: Invalid template variable " << tv <<
"\n";
635 ctx[var.at(0)] = var.at(1);
638 auto diagram_template_str =
641 YAML::Node diagram_node;
644 auto diagram_str = inja::render(diagram_template_str, ctx);
645 diagram_node = YAML::Load(diagram_str);
647 catch (inja::InjaError &e) {
648 std::cerr <<
"ERROR: Failed to generate diagram template '"
649 << template_name <<
"': " << e.what() <<
"\n";
652 catch (YAML::Exception &e) {
653 std::cerr <<
"ERROR: Rendering diagram template '" << template_name
654 <<
"' resulted in invalid YAML: " << e.what() <<
"\n";
658 namespace fs = std::filesystem;
660 fs::path config_file{config_file_path};
662 if (!fs::exists(config_file)) {
663 std::cerr <<
"ERROR: " << config_file_path <<
" file doesn't exists\n";
667 YAML::Node doc = YAML::LoadFile(config_file.string());
669 const auto diagram_name = diagram_node.begin()->first.as<std::string>();
670 doc[
"diagrams"][diagram_name] = diagram_node.begin()->second;
676 out << YAML::Newline;
678 std::ofstream ofs(config_file);
691 out << YAML::Newline;
693 ostr_ << out.c_str();