0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
cli_handler.cc
Go to the documentation of this file.
1/**
2 * @file src/options/cli_handler.cc
3 *
4 * Copyright (c) 2021-2025 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#include "cli_handler.h"
19
20#include "util/util.h"
21#include "version/version.h"
22
23#include <clang/Basic/Version.h>
24#include <indicators/indicators.hpp>
25
26namespace clanguml::cli {
28 std::ostream &ostr, std::shared_ptr<spdlog::logger> logger)
29 : ostr_{ostr}
30 , logger_{std::move(logger)}
31{
32}
33
35{
36 spdlog::drop("clanguml-logger");
37
38 spdlog::register_logger(logger_);
39
40 if (logger_type == logging::logger_type_t::text) {
41 clanguml::logging::logger_type(logging::logger_type_t::text);
42 logger_->set_pattern("%^[%l]%$ [tid %t] %v");
43 }
44 else {
45 clanguml::logging::logger_type(logging::logger_type_t::json);
46 logger_->set_pattern("{\"time\": \"%Y-%m-%dT%H:%M:%S.%f%z\", \"name\": "
47 "\"%n\", \"level\": \"%^%l%$\", "
48 "\"thread\": %t, %v}");
49 if (progress) {
51 }
52 }
53
54 if (verbose == 0) { // --quiet
55 logger_->set_level(spdlog::level::err);
56 }
57 else if (verbose == 1) { // [default]
58 logger_->set_level(spdlog::level::info);
59 }
60 else if (verbose == 2) { // -v
61 logger_->set_level(spdlog::level::debug);
62 }
63 else { // -vv
64 logger_->set_level(spdlog::level::trace);
65 }
66}
68{
69 spdlog::drop("json-progress-logger");
70
71 auto json_progress_logger = spdlog::stdout_color_mt(
72 "json-progress-logger", spdlog::color_mode::automatic);
73
74 if (sink) {
75 json_progress_logger->sinks().clear();
76 json_progress_logger->sinks().emplace_back(std::move(sink));
77 }
78
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}");
84}
85
86cli_flow_t cli_handler::parse(int argc, const char **argv)
87{
88 static const std::map<std::string, clanguml::common::generator_type_t>
89 generator_type_names{
94
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}};
98
99 app.add_option("-c,--config", config_path,
100 "Location of configuration file, when '-' read from stdin");
101 app.add_option("-d,--compile-database", compilation_database_dir,
102 "Location of compilation database directory");
103 app.add_option(
104 "-n,--diagram-name", diagram_names, "Name of diagram to generate");
105 app.add_option("-g,--generator", generators,
106 "Name of the generator: plantuml, mermaid, json or graphml "
107 "(default: "
108 "plantuml)")
109 ->transform(CLI::CheckedTransformer(generator_type_names))
110 ->option_text("TEXT ...");
111 app.add_option("-o,--output-directory", output_directory,
112 "Override output directory specified in config file");
113 app.add_option("-t,--thread-count", thread_count,
114 "Thread pool size (0 = hardware concurrency)");
115 app.add_flag("-V,--version", show_version, "Print version and exit");
116 app.add_flag("-v,--verbose", verbose,
117 "Verbose logging ('-v' - debug, '-vv' - trace)");
118 app.add_option(
119 "--logger", logger_type, "Log format: text, json (default: text)")
120 ->transform(CLI::CheckedTransformer(logger_type_names))
121 ->option_text("TEXT ...");
122 app.add_flag(
123 "-p,--progress", progress, "Show progress bars for generated diagrams");
124 app.add_flag("-q,--quiet", quiet, "Minimal logging");
125 app.add_flag("-l,--list-diagrams", list_diagrams,
126 "Print list of diagram names defined in the config file");
127 app.add_flag("--init", initialize, "Initialize example config file");
128 app.add_option("--add-compile-flag", add_compile_flag,
129 "Add a compilation flag to each entry in the compilation database");
130 app.add_option("--remove-compile-flag", remove_compile_flag,
131 "Remove a compilation flag from each entry in the compilation "
132 "database");
133#if !defined(_WIN32)
134 app.add_option("--query-driver", query_driver,
135 "Query the specific compiler driver to extract system paths and add "
136 "them to compile commands (e.g. arm-none-eabi-g++)");
137#endif
138 app.add_flag("--allow-empty-diagrams", allow_empty_diagrams,
139 "Do not raise an error when generated diagram model is empty");
140 app.add_option("--add-class-diagram", add_class_diagram,
141 "Add example class diagram to config file");
142 app.add_option("--add-sequence-diagram", add_sequence_diagram,
143 "Add example sequence diagram to config file");
144 app.add_option("--add-package-diagram", add_package_diagram,
145 "Add example package diagram to config file");
146 app.add_option("--add-include-diagram", add_include_diagram,
147 "Add example include diagram to config");
148 app.add_option("--add-diagram-from-template", add_diagram_from_template,
149 "Add diagram config based on diagram template");
150 app.add_option("--generate-from-template", generate_from_template,
151 "Generate diagram from template without adding it to config");
152 app.add_option("--template-var", template_variables,
153 "Specify a value for a template variable");
154 app.add_flag("--list-templates", list_templates,
155 "List all available diagram templates");
156 app.add_option("--show-template", show_template,
157 "Show specific diagram template definition");
158 app.add_flag(
159 "--dump-config", dump_config, "Print effective config to stdout");
160 app.add_flag("--paths-relative-to-pwd", paths_relative_to_pwd,
161 "If true, all paths in configuration files are relative to the $PWD "
162 "instead of actual location of `.clang-uml` file.");
163 app.add_flag("--no-metadata", no_metadata,
164 "Skip metadata (e.g. clang-uml version) from diagrams");
165 app.add_flag("--print-from,--print-start-from", print_from,
166 "Print all possible 'from' values for a given diagram");
167 app.add_flag("--print-to", print_to,
168 "Print all possible 'to' values for a given diagram");
169 app.add_flag("--no-validate", no_validate,
170 "Do not perform configuration file schema validation");
171 app.add_flag("--validate-only", validate_only,
172 "Perform configuration file schema validation and exit");
173 app.add_flag("-r,--render_diagrams", render_diagrams,
174 "Automatically render generated diagrams using appropriate command");
175 app.add_option("--plantuml-cmd", plantuml_cmd,
176 "Command template to render PlantUML diagram, `{}` will be replaced "
177 "with diagram name.");
178 app.add_option("--mermaid-cmd", mermaid_cmd,
179 "Command template to render MermaidJS diagram, `{}` will be replaced "
180 "with diagram name.");
181 app.add_option(
182 "--user-data",
183 [this](CLI::results_t vals) {
184 for (const auto &val : vals) {
185 auto res = util::split_at_first("=", val);
186
187 if (!res) {
188 throw CLI::ValidationError(
189 fmt::format("Invalid option '--user-data {}'", val),
190 "User data must be of the form '--user-data "
191 "key=value'");
192 }
193
194 user_data.emplace_back(std::move(*res));
195 }
196
197 return true;
198 },
199 "Add custom data properties to Jinja context available in the "
200 "diagrams")
201 ->take_all()
202 ->expected(1, -1);
203
204 try {
205 app.parse(argc, argv);
206 }
207 catch (const CLI::CallForHelp &e) {
208 exit(app.exit(e)); // NOLINT(concurrency-mt-unsafe)
209 }
210 catch (const CLI::Success &e) {
211 return cli_flow_t::kExit;
212 }
213 catch (const CLI::ParseError &e) {
214 exit(app.exit(e)); // NOLINT(concurrency-mt-unsafe)
215 }
216
218 verbose = 0;
219 else
220 verbose++;
221
222 if (progress && (logger_type == logging::logger_type_t::text))
223 verbose = 0;
224
226}
227
228cli_flow_t cli_handler::handle_options(int argc, const char **argv)
229{
230 auto res = parse(argc, argv);
231
232 if (res != cli_flow_t::kContinue)
233 return res;
234
236
238
239 if (res != cli_flow_t::kContinue)
240 return res;
241
242 res = load_config();
243
244 if (res != cli_flow_t::kContinue)
245 return res;
246
248
249 config.inherit();
250
251 if (progress && (logging::logger_type() == logging::logger_type_t::text)) {
252 spdlog::drop("clanguml-logger");
253
254 // Setup null logger for clean progress indicators
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_);
259 }
260
261 return res;
262}
263
265{
266 if (show_version) {
267 return print_version();
268 }
269
270 if ((config_path == "-") &&
272 add_class_diagram.has_value() || add_sequence_diagram.has_value() ||
273 add_package_diagram.has_value() ||
274 add_include_diagram.has_value())) {
275
276 LOG_ERROR(
277 "ERROR: Cannot add a diagram config to configuration from stdin");
278
279 return cli_flow_t::kError;
280 }
281
282 if (print_from || print_to) {
283 if (diagram_names.size() != 1) {
284 LOG_ERROR("ERROR: '--print-from' and '--print-to' require "
285 "specifying one diagram name using '-n' option");
286
287 return cli_flow_t::kError;
288 }
289 }
290
291 if (initialize) {
292 return create_config_file();
293 }
294
295 if (config_path != "-") {
296 if (add_class_diagram) {
297 return add_config_diagram(
300 }
301
303 return add_config_diagram(
306 }
307
309 return add_config_diagram(
312 }
313
315 return add_config_diagram(
318 }
319 }
320
322}
323
325{
326 try {
329 if (validate_only) {
330 if (logger_type == logging::logger_type_t::text) {
331 ostr_ << "Configuration file " << config_path << " is valid.\n";
332 }
333 else {
334 inja::json j;
335 j["valid"] = true;
336 ostr_ << j.dump();
337 }
338 return cli_flow_t::kExit;
339 }
340
342 }
345 }
346 catch (std::runtime_error &e) {
347 LOG_ERROR(e.what());
348 }
349
350 return cli_flow_t::kError;
351}
352
354{
355 if (dump_config) {
356 return print_config();
357 }
358
359 if (list_diagrams) {
360 return print_diagrams_list();
361 }
362
363 if (list_templates) {
365 }
366
367 if (show_template) {
369 }
370
374 }
375
378 }
379
380 LOG_INFO("Loaded clang-uml config from {}", config_path);
381
384 }
385
386 if (auto r = add_custom_user_data(); r != cli_flow_t::kContinue)
387 return r;
388
389 //
390 // Override selected config options from command line
391 //
395 .string());
396 }
397
399
400 // Override the output directory from the config
401 // with the value from the command line if any
404
405 if (output_directory) {
408 }
409
410 LOG_INFO("Loading compilation database from {} directory",
412
414 return cli_flow_t::kError;
415
416 //
417 // Append add_compile_flags and remove_compile_flags to the config
418 //
419 if (add_compile_flag) {
420 std::copy(add_compile_flag->begin(), add_compile_flag->end(),
421 std::back_inserter(config.add_compile_flags.value));
423 }
424
426 std::copy(remove_compile_flag->begin(), remove_compile_flag->end(),
427 std::back_inserter(config.remove_compile_flags.value));
428 config.remove_compile_flags.has_value = true;
429 }
430
431 if (plantuml_cmd) {
432 if (!config.puml)
433 config.puml.set({});
434
435 config.puml().cmd = plantuml_cmd.value();
436 }
437
438 if (mermaid_cmd) {
439 if (!config.mermaid)
440 config.mermaid.set({});
441
442 config.mermaid().cmd = mermaid_cmd.value();
443 }
444
445#if !defined(_WIN32)
446 if (query_driver) {
448 }
449#endif
450
452}
453
455{
456 runtime_config cfg;
458 cfg.verbose = verbose;
460 cfg.print_to = print_to;
461 cfg.progress = progress;
465
466 return cfg;
467}
468
469void cli_handler::set_config_path(const std::string &path)
470{
471 config_path = path;
472}
473
475{
476 if (logger_type == clanguml::logging::logger_type_t::text) {
477 ostr_ << "clang-uml " << clanguml::version::version() << '\n';
478 ostr_ << "Copyright (C) 2021-2025 Bartek Kryza <bkryza@gmail.com>"
479 << '\n';
480 ostr_ << util::get_os_name() << '\n';
481 ostr_ << "Built against LLVM/Clang libraries version: "
482 << LLVM_VERSION_STRING << '\n';
483 ostr_ << "Using LLVM/Clang libraries version: "
484 << clang::getClangFullVersion() << '\n';
485 }
486 else {
487 nlohmann::json j;
488 j["version"] = clanguml::version::version();
489 j["copyright"] =
490 "Copyright (C) 2021-2025 Bartek Kryza <bkryza@gmail.com>";
491 j["llvm"]["built_with"] = LLVM_VERSION_STRING;
492 j["llvm"]["using"] = clang::getClangFullVersion();
493 ostr_ << j;
494 }
495
496 return cli_flow_t::kExit;
497}
498
500{
501 namespace fs = std::filesystem;
502 using std::cout;
503
504 fs::path output_dir{dir};
505
506 if (fs::exists(output_dir) && !fs::is_directory(output_dir)) {
507 cout << "ERROR: " << dir << " is not a directory...\n";
508 return false;
509 }
510
511 if (!fs::exists(output_dir)) {
512 return fs::create_directories(output_dir);
513 }
514
515 return true;
516}
517
519{
520 using std::cout;
521
522 if (logger_type == logging::logger_type_t::text) {
523 ostr_ << "The following diagrams are defined in the config file:\n";
524 for (const auto &[name, diagram] : config.diagrams) {
525 ostr_ << " - " << name << " [" << to_string(diagram->type())
526 << "]";
527 ostr_ << '\n';
528 }
529 }
530 else {
531 inja::json j = inja::json::array();
532 for (const auto &[name, diagram] : config.diagrams) {
533 inja::json d;
534 d["name"] = name;
535 d["type"] = to_string(diagram->type());
536 j.emplace_back(std::move(d));
537 }
538
539 ostr_ << j.dump();
540 }
541
542 return cli_flow_t::kExit;
543}
544
546{
547 using std::cout;
548
550 if (logger_type == logging::logger_type_t::text) {
551 ostr_ << "No diagram templates are defined in the config file\n";
552 }
553 else {
554 ostr_ << "[]";
555 }
556 return cli_flow_t::kExit;
557 }
558 if (logger_type == logging::logger_type_t::text) {
559 ostr_ << "The following diagram templates are available:\n";
560 for (const auto &[name, diagram_template] :
562 ostr_ << " - " << name << " [" << to_string(diagram_template.type)
563 << "]";
564 if (!diagram_template.description.empty())
565 ostr_ << ": " << diagram_template.description;
566 ostr_ << '\n';
567 }
568 }
569 else {
570 inja::json j = inja::json::array();
571 for (const auto &[name, diagram_template] :
573 inja::json dt;
574 dt["name"] = name;
575 dt["type"] = to_string(diagram_template.type);
576 dt["description"] = diagram_template.description;
577 j.emplace_back(std::move(dt));
578 }
579 ostr_ << j.dump();
580 }
581
582 return cli_flow_t::kExit;
583}
584
585cli_flow_t cli_handler::print_diagram_template(const std::string &template_name)
586{
588 config.diagram_templates().count(template_name) == 0) {
589 ostr_ << "No such diagram template: " << template_name << "\n";
590 return cli_flow_t::kError;
591 }
592
593 for (const auto &[name, diagram_template] : config.diagram_templates()) {
594 if (template_name == name) {
595 ostr_ << diagram_template.jinja_template << "\n";
596 return cli_flow_t::kExit;
597 }
598 }
599
600 return cli_flow_t::kError;
601}
602
604{
605 namespace fs = std::filesystem;
606
607 fs::path config_file{config_path};
608
609 if (fs::exists(config_file)) {
610 ostr_ << "ERROR: .clang-uml file already exists\n";
611 return cli_flow_t::kError;
612 }
613
614 YAML::Emitter out;
615 out.SetIndent(2);
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 << ".";
619 out << YAML::Newline
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;
635 out << YAML::EndMap;
636 out << YAML::Key << "exclude";
637 out << YAML::BeginMap;
638 out << YAML::Key << "namespaces";
639 out << YAML::BeginSeq << "myproject::detail" << YAML::EndSeq;
640 out << YAML::EndMap;
641 out << YAML::EndMap;
642 out << YAML::EndMap;
643 out << YAML::EndMap;
644 out << YAML::Newline;
645
646 std::ofstream ofs(config_file);
647 ofs << out.c_str();
648 ofs.close();
649
650 return cli_flow_t::kExit;
651}
652
655 const std::string &config_file_path, const std::string &name)
656{
657 namespace fs = std::filesystem;
658
659 fs::path config_file{config_file_path};
660
661 if (!fs::exists(config_file)) {
662 std::cerr << "ERROR: " << config_file_path << " file doesn't exists\n";
663 return cli_flow_t::kError;
664 }
665
666 YAML::Node doc = YAML::LoadFile(config_file.string());
667
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";
673 return cli_flow_t::kError;
674 }
675 }
676
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"}};
686 }
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"] =
691 true;
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 **)"}}};
705 }
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"}};
715 }
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"}};
722 }
723
724 YAML::Emitter out;
725 out.SetIndent(2);
726
727 out << doc;
728 out << YAML::Newline;
729
730 std::ofstream ofs(config_file);
731 ofs << out.c_str();
732 ofs.close();
733
734 return cli_flow_t::kExit;
735}
736
738 const std::string &template_name, YAML::Node &diagram_node)
739{
741 !(config.diagram_templates().find(template_name) !=
742 config.diagram_templates().end())) {
743 std::cerr << "ERROR: No such diagram template: " << template_name
744 << "\n";
745 return cli_flow_t::kError;
746 }
747
748 // First, try to render the template using inja and create a YAML node
749 // from it
750 inja::json ctx;
751 for (const auto &tv : template_variables) {
752 const auto var = util::split(tv, "=");
753 if (var.size() != 2) {
754 std::cerr << "ERROR: Invalid template variable " << tv << "\n";
755 return cli_flow_t::kError;
756 }
757
758 ctx[var.at(0)] = var.at(1);
759 }
760
761 auto diagram_template_str =
762 config.diagram_templates().at(template_name).jinja_template;
763 try {
764 auto diagram_str = inja::render(diagram_template_str, ctx);
765 diagram_node = YAML::Load(diagram_str);
766 }
767 catch (inja::InjaError &e) {
768 std::cerr << "ERROR: Failed to generate diagram template '"
769 << template_name << "': " << e.what() << "\n";
770 return cli_flow_t::kError;
771 }
772 catch (YAML::Exception &e) {
773 std::cerr << "ERROR: Rendering diagram template '" << template_name
774 << "' resulted in invalid YAML: " << e.what() << "\n";
775 return cli_flow_t::kError;
776 }
777
779}
780
782 const std::string &config_file_path, const std::string &template_name)
783{
784 YAML::Node diagram_node;
785 const auto res = render_diagram_template(template_name, diagram_node);
786
787 if (res == cli_flow_t::kError)
788 return res;
789
790 namespace fs = std::filesystem;
791
792 fs::path config_file{config_file_path};
793
794 if (!fs::exists(config_file)) {
795 std::cerr << "ERROR: " << config_file_path << " file doesn't exists\n";
796 return cli_flow_t::kError;
797 }
798
799 YAML::Node doc = YAML::LoadFile(config_file.string());
800
801 const auto diagram_name = diagram_node.begin()->first.as<std::string>();
802 doc["diagrams"][diagram_name] = diagram_node.begin()->second;
803
804 YAML::Emitter out;
805 out.SetIndent(2);
806
807 out << doc;
808 out << YAML::Newline;
809
810 std::ofstream ofs(config_file);
811 ofs << out.c_str();
812 ofs.close();
813
814 return cli_flow_t::kExit;
815}
816
818 const std::string &template_name)
819{
820 YAML::Node diagram_node;
821 const auto res = render_diagram_template(template_name, diagram_node);
822
823 if (res == cli_flow_t::kError)
824 return res;
825
826 const auto diagram_name = diagram_node.begin()->first.as<std::string>();
827
828 auto diagram_config =
829 YAML::parse_diagram_config(diagram_node.begin()->second);
830 if (diagram_config) {
831 diagram_config->name = diagram_name;
832 config.diagrams[diagram_name] = std::move(diagram_config);
833 diagram_names.push_back(diagram_name);
834 }
835 else {
836 return cli_flow_t::kError;
837 }
838
840}
841
843{
844 YAML::Emitter out;
845 out.SetIndent(2);
846
847 out << config;
848 out << YAML::Newline;
849
850 ostr_ << out.c_str();
851
852 return cli_flow_t::kExit;
853}
854
856{
857 for (const auto &[key_path, value] : user_data) {
858 auto path = util::split(key_path, ".");
859 auto *user_data_it = &config.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");
864 return cli_flow_t::kError;
865 }
866
867 user_data_it = &((*user_data_it)[key]);
868 }
869
870 *user_data_it = value;
871 }
872
874}
875
876} // namespace clanguml::cli