23#include <clang/Basic/Version.h>
24#include <indicators/indicators.hpp>
28 std::ostream &ostr, std::shared_ptr<spdlog::logger> logger)
30 , logger_{
std::move(logger)}
36 spdlog::drop(
"clanguml-logger");
38 spdlog::register_logger(
logger_);
42 logger_->set_pattern(
"%^[%l]%$ [tid %t] %v");
46 logger_->set_pattern(
"{\"time\": \"%Y-%m-%dT%H:%M:%S.%f%z\", \"name\": "
47 "\"%n\", \"level\": \"%^%l%$\", "
48 "\"thread\": %t, %v}");
55 logger_->set_level(spdlog::level::err);
58 logger_->set_level(spdlog::level::info);
61 logger_->set_level(spdlog::level::debug);
64 logger_->set_level(spdlog::level::trace);
69 spdlog::drop(
"json-progress-logger");
71 auto json_progress_logger = spdlog::stdout_color_mt(
72 "json-progress-logger", spdlog::color_mode::automatic);
75 json_progress_logger->sinks().clear();
76 json_progress_logger->sinks().emplace_back(std::move(sink));
79 json_progress_logger->set_level(spdlog::level::info);
80 json_progress_logger->set_pattern(
81 "{\"time\": \"%Y-%m-%dT%H:%M:%S.%f%z\", \"name\": "
82 "\"%n\", \"level\": \"%^%l%$\", "
83 "\"thread\": %t, \"progress\": %v}");
88 static const std::map<std::string, clanguml::common::generator_type_t>
95 static const std::map<std::string, clanguml::logging::logger_type_t>
96 logger_type_names{{
"text", clanguml::logging::logger_type_t::text},
97 {
"json", clanguml::logging::logger_type_t::json}};
100 "Location of configuration file, when '-' read from stdin");
102 "Location of compilation database directory");
104 "-n,--diagram-name",
diagram_names,
"Name of diagram to generate");
106 "Name of the generator: plantuml, mermaid, json or graphml "
109 ->transform(CLI::CheckedTransformer(generator_type_names))
110 ->option_text(
"TEXT ...");
112 "Override output directory specified in config file");
114 "Thread pool size (0 = hardware concurrency)");
117 "Verbose logging ('-v' - debug, '-vv' - trace)");
119 "--logger",
logger_type,
"Log format: text, json (default: text)")
120 ->transform(CLI::CheckedTransformer(logger_type_names))
121 ->option_text(
"TEXT ...");
123 "-p,--progress",
progress,
"Show progress bars for generated diagrams");
124 app.add_flag(
"-q,--quiet",
quiet,
"Minimal logging");
126 "Print list of diagram names defined in the config file");
127 app.add_flag(
"--init",
initialize,
"Initialize example config file");
129 "Add a compilation flag to each entry in the compilation database");
131 "Remove a compilation flag from each entry in the compilation "
135 "Query the specific compiler driver to extract system paths and add "
136 "them to compile commands (e.g. arm-none-eabi-g++)");
139 "Do not raise an error when generated diagram model is empty");
141 "Add example class diagram to config file");
143 "Add example sequence diagram to config file");
145 "Add example package diagram to config file");
147 "Add example include diagram to config");
149 "Add diagram config based on diagram template");
151 "Generate diagram from template without adding it to config");
153 "Specify a value for a template variable");
155 "List all available diagram templates");
157 "Show specific diagram template definition");
159 "--dump-config",
dump_config,
"Print effective config to stdout");
161 "If true, all paths in configuration files are relative to the $PWD "
162 "instead of actual location of `.clang-uml` file.");
164 "Skip metadata (e.g. clang-uml version) from diagrams");
166 "Print all possible 'from' values for a given diagram");
168 "Print all possible 'to' values for a given diagram");
170 "Do not perform configuration file schema validation");
172 "Perform configuration file schema validation and exit");
174 "Automatically render generated diagrams using appropriate command");
176 "Command template to render PlantUML diagram, `{}` will be replaced "
177 "with diagram name.");
179 "Command template to render MermaidJS diagram, `{}` will be replaced "
180 "with diagram name.");
183 [
this](CLI::results_t vals) {
184 for (
const auto &val : vals) {
188 throw CLI::ValidationError(
189 fmt::format(
"Invalid option '--user-data {}'", val),
190 "User data must be of the form '--user-data "
199 "Add custom data properties to Jinja context available in the "
205 app.parse(argc, argv);
207 catch (
const CLI::CallForHelp &e) {
210 catch (
const CLI::Success &e) {
213 catch (
const CLI::ParseError &e) {
230 auto res =
parse(argc, argv);
252 spdlog::drop(
"clanguml-logger");
255 std::vector<spdlog::sink_ptr> sinks;
256 logger_ = std::make_shared<spdlog::logger>(
257 "clanguml-logger", begin(sinks), end(sinks));
258 spdlog::register_logger(
logger_);
277 "ERROR: Cannot add a diagram config to configuration from stdin");
284 LOG_ERROR(
"ERROR: '--print-from' and '--print-to' require "
285 "specifying one diagram name using '-n' option");
346 catch (std::runtime_error &e) {
410 LOG_INFO(
"Loading compilation database from {} directory",
476 if (
logger_type == clanguml::logging::logger_type_t::text) {
478 ostr_ <<
"Copyright (C) 2021-2025 Bartek Kryza <bkryza@gmail.com>"
481 ostr_ <<
"Built against LLVM/Clang libraries version: "
482 << LLVM_VERSION_STRING <<
'\n';
483 ostr_ <<
"Using LLVM/Clang libraries version: "
484 << clang::getClangFullVersion() <<
'\n';
490 "Copyright (C) 2021-2025 Bartek Kryza <bkryza@gmail.com>";
491 j[
"llvm"][
"built_with"] = LLVM_VERSION_STRING;
492 j[
"llvm"][
"using"] = clang::getClangFullVersion();
501 namespace fs = std::filesystem;
504 fs::path output_dir{dir};
506 if (fs::exists(output_dir) && !fs::is_directory(output_dir)) {
507 cout <<
"ERROR: " << dir <<
" is not a directory...\n";
511 if (!fs::exists(output_dir)) {
512 return fs::create_directories(output_dir);
523 ostr_ <<
"The following diagrams are defined in the config file:\n";
525 ostr_ <<
" - " << name <<
" [" << to_string(diagram->type())
531 inja::json j = inja::json::array();
535 d[
"type"] = to_string(diagram->type());
536 j.emplace_back(std::move(d));
551 ostr_ <<
"No diagram templates are defined in the config file\n";
559 ostr_ <<
"The following diagram templates are available:\n";
560 for (
const auto &[name, diagram_template] :
562 ostr_ <<
" - " << name <<
" [" << to_string(diagram_template.type)
564 if (!diagram_template.description.empty())
565 ostr_ <<
": " << diagram_template.description;
570 inja::json j = inja::json::array();
571 for (
const auto &[name, diagram_template] :
575 dt[
"type"] = to_string(diagram_template.type);
576 dt[
"description"] = diagram_template.description;
577 j.emplace_back(std::move(dt));
589 ostr_ <<
"No such diagram template: " << template_name <<
"\n";
594 if (template_name == name) {
595 ostr_ << diagram_template.jinja_template <<
"\n";
605 namespace fs = std::filesystem;
609 if (fs::exists(config_file)) {
610 ostr_ <<
"ERROR: .clang-uml file already exists\n";
616 out << YAML::BeginMap;
617 out << YAML::Comment(
"Change to directory where compile_commands.json is");
618 out << YAML::Key <<
"compilation_database_dir" << YAML::Value <<
".";
620 << YAML::Comment(
"Change to directory where diagram should be written");
621 out << YAML::Key <<
"output_directory" << YAML::Value <<
"docs/diagrams";
622 out << YAML::Key <<
"diagrams" << YAML::Value;
623 out << YAML::BeginMap;
624 out << YAML::Key <<
"example_class_diagram" << YAML::Value;
625 out << YAML::BeginMap;
626 out << YAML::Key <<
"type" << YAML::Value <<
"class";
627 out << YAML::Key <<
"glob" << YAML::Value;
628 out << YAML::BeginSeq <<
"src/*.cpp" << YAML::EndSeq;
629 out << YAML::Key <<
"using_namespace" << YAML::Value;
630 out << YAML::BeginSeq <<
"myproject" << YAML::EndSeq;
631 out << YAML::Key <<
"include";
632 out << YAML::BeginMap;
633 out << YAML::Key <<
"namespaces";
634 out << YAML::BeginSeq <<
"myproject" << YAML::EndSeq;
636 out << YAML::Key <<
"exclude";
637 out << YAML::BeginMap;
638 out << YAML::Key <<
"namespaces";
639 out << YAML::BeginSeq <<
"myproject::detail" << YAML::EndSeq;
644 out << YAML::Newline;
646 std::ofstream ofs(config_file);
655 const std::string &config_file_path,
const std::string &name)
657 namespace fs = std::filesystem;
659 fs::path config_file{config_file_path};
661 if (!fs::exists(config_file)) {
662 std::cerr <<
"ERROR: " << config_file_path <<
" file doesn't exists\n";
666 YAML::Node doc = YAML::LoadFile(config_file.string());
668 for (YAML::const_iterator it = doc[
"diagrams"].begin();
669 it != doc[
"diagrams"].end(); ++it) {
670 if (it->first.as<std::string>() == name) {
671 std::cerr <<
"ERROR: " << config_file_path
672 <<
" file already contains '" << name <<
"' diagram";
678 doc[
"diagrams"][name][
"type"] =
"class";
679 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
680 doc[
"diagrams"][name][
"using_namespace"] =
681 std::vector<std::string>{{
"myproject"}};
682 doc[
"diagrams"][name][
"include"][
"namespaces"] =
683 std::vector<std::string>{{
"myproject"}};
684 doc[
"diagrams"][name][
"exclude"][
"namespaces"] =
685 std::vector<std::string>{{
"myproject::detail"}};
688 doc[
"diagrams"][name][
"type"] =
"sequence";
689 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
690 doc[
"diagrams"][name][
"combine_free_functions_into_file_participants"] =
692 doc[
"diagrams"][name][
"inline_lambda_messages"] =
false;
693 doc[
"diagrams"][name][
"generate_message_comments"] =
false;
694 doc[
"diagrams"][name][
"fold_repeated_activities"] =
false;
695 doc[
"diagrams"][name][
"generate_condition_statements"] =
false;
696 doc[
"diagrams"][name][
"using_namespace"] =
697 std::vector<std::string>{{
"myproject"}};
698 doc[
"diagrams"][name][
"include"][
"paths"] =
699 std::vector<std::string>{{
"src"}};
700 doc[
"diagrams"][name][
"exclude"][
"namespaces"] =
701 std::vector<std::string>{{
"myproject::detail"}};
702 doc[
"diagrams"][name][
"start_from"] =
703 std::vector<std::map<std::string, std::string>>{
704 {{
"function",
"main(int,const char **)"}}};
707 doc[
"diagrams"][name][
"type"] =
"package";
708 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
709 doc[
"diagrams"][name][
"using_namespace"] =
710 std::vector<std::string>{{
"myproject"}};
711 doc[
"diagrams"][name][
"include"][
"namespaces"] =
712 std::vector<std::string>{{
"myproject"}};
713 doc[
"diagrams"][name][
"exclude"][
"namespaces"] =
714 std::vector<std::string>{{
"myproject::detail"}};
717 doc[
"diagrams"][name][
"type"] =
"include";
718 doc[
"diagrams"][name][
"glob"] = std::vector<std::string>{{
"src/*.cpp"}};
719 doc[
"diagrams"][name][
"relative_to"] =
".";
720 doc[
"diagrams"][name][
"include"][
"paths"] =
721 std::vector<std::string>{{
"src"}};
728 out << YAML::Newline;
730 std::ofstream ofs(config_file);
738 const std::string &template_name, YAML::Node &diagram_node)
743 std::cerr <<
"ERROR: No such diagram template: " << template_name
753 if (var.size() != 2) {
754 std::cerr <<
"ERROR: Invalid template variable " << tv <<
"\n";
758 ctx[var.at(0)] = var.at(1);
761 auto diagram_template_str =
764 auto diagram_str = inja::render(diagram_template_str, ctx);
765 diagram_node = YAML::Load(diagram_str);
767 catch (inja::InjaError &e) {
768 std::cerr <<
"ERROR: Failed to generate diagram template '"
769 << template_name <<
"': " << e.what() <<
"\n";
772 catch (YAML::Exception &e) {
773 std::cerr <<
"ERROR: Rendering diagram template '" << template_name
774 <<
"' resulted in invalid YAML: " << e.what() <<
"\n";
782 const std::string &config_file_path,
const std::string &template_name)
784 YAML::Node diagram_node;
790 namespace fs = std::filesystem;
792 fs::path config_file{config_file_path};
794 if (!fs::exists(config_file)) {
795 std::cerr <<
"ERROR: " << config_file_path <<
" file doesn't exists\n";
799 YAML::Node doc = YAML::LoadFile(config_file.string());
801 const auto diagram_name = diagram_node.begin()->first.as<std::string>();
802 doc[
"diagrams"][diagram_name] = diagram_node.begin()->second;
808 out << YAML::Newline;
810 std::ofstream ofs(config_file);
818 const std::string &template_name)
820 YAML::Node diagram_node;
826 const auto diagram_name = diagram_node.begin()->first.as<std::string>();
828 auto diagram_config =
830 if (diagram_config) {
831 diagram_config->
name = diagram_name;
848 out << YAML::Newline;
850 ostr_ << out.c_str();
857 for (
const auto &[key_path, value] :
user_data) {
860 for (
const auto &key : path) {
861 if (!user_data_it->is_object() && !user_data_it->empty()) {
862 LOG_ERROR(
"Setting custom --user-data is only possible if "
863 "`user_data` in config file is empty or an object");
867 user_data_it = &((*user_data_it)[key]);
870 *user_data_it = value;