0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
yaml_decoders.cc
Go to the documentation of this file.
1/**
2 * @file src/config/yaml_decoders.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 "cli/cli_handler.h"
20#include "config.h"
21#include "diagram_templates.h"
22#include "schema.h"
23
24#define MIROIR_IMPLEMENTATION
25#define MIROIR_YAMLCPP_SPECIALIZATION
26#include <miroir/miroir.hpp>
27
28namespace YAML {
57
58inline bool has_key(const YAML::Node &n, const std::string &key)
59{
60 assert(n.Type() == NodeType::Map);
61
62 return std::count_if(n.begin(), n.end(), [&key](auto &&n) {
63 return n.first.template as<std::string>() == key;
64 }) > 0;
65}
66
67template <typename T>
68void get_option(const Node &node, clanguml::config::option<T> &option)
69{
70 if (node[option.name])
71 option.set(node[option.name].template as<T>());
72
73 for (const auto &alt_name : option.alternate_names)
74 if (node[alt_name]) {
75 option.set(node[alt_name].template as<T>());
76 break;
77 }
78}
79
80template <>
83{
84 if (node[option.name]) {
85 if (node[option.name].Type() == NodeType::Scalar)
86 option.set({node[option.name].template as<std::string>()});
87 else if (node[option.name].Type() == NodeType::Sequence)
88 option.set(
89 {node[option.name].template as<std::vector<std::string>>()[0]});
90 else
91 throw std::runtime_error("Invalid using_namespace value");
92 }
93}
94
95template <>
96void get_option<method_arguments>(
97 const Node &node, clanguml::config::option<method_arguments> &option)
98{
99 if (node[option.name]) {
100 const auto &val = node[option.name].as<std::string>();
101 if (val == "full")
102 option.set(method_arguments::full);
103 else if (val == "abbreviated")
104 option.set(method_arguments::abbreviated);
105 else if (val == "none")
106 option.set(method_arguments::none);
107 else
108 throw std::runtime_error(
109 "Invalid generate_method_arguments value: " + val);
110 }
111}
112
113template <>
114void get_option<member_order_t>(
115 const Node &node, clanguml::config::option<member_order_t> &option)
116{
117 if (node[option.name]) {
118 const auto &val = node[option.name].as<std::string>();
119 if (val == "as_is")
120 option.set(member_order_t::as_is);
121 else if (val == "lexical")
122 option.set(member_order_t::lexical);
123 else
124 throw std::runtime_error("Invalid member_order value: " + val);
125 }
126}
127
128template <>
129void get_option<package_type_t>(
130 const Node &node, clanguml::config::option<package_type_t> &option)
131{
132 if (node[option.name]) {
133 const auto &val = node[option.name].as<std::string>();
134 if (val == "namespace")
135 option.set(package_type_t::kNamespace);
136 else if (val == "directory")
137 option.set(package_type_t::kDirectory);
138 else if (val == "module")
139 option.set(package_type_t::kModule);
140 else
141 throw std::runtime_error(
142 "Invalid generate_method_arguments value: " + val);
143 }
144}
145
146template <>
147void get_option<clanguml::config::comment_parser_t>(const Node &node,
149{
150 if (node[option.name]) {
151 const auto &val = node[option.name].as<std::string>();
152 if (val == "plain")
154 else if (val == "clang")
156 else
157 throw std::runtime_error("Invalid comment_parser value: " + val);
158 }
159}
160
161template <>
162void get_option<clanguml::config::filter_mode_t>(const Node &node,
164{
165 if (node[option.name]) {
166 const auto &val = node[option.name].as<std::string>();
167 if (val == "basic")
169 else if (val == "advanced")
171 else
172 throw std::runtime_error("Invalid comment_parser value: " + val);
173 }
174}
175
176template <>
177void get_option<std::map<std::string, clanguml::config::diagram_template>>(
178 const Node &node,
180 std::map<std::string, clanguml::config::diagram_template>> &option)
181{
182 if (!node[option.name]) {
183 return;
184 }
185
186 if (node[option.name].IsMap() && node[option.name]["include!"]) {
187 auto parent_path = node["__parent_path"].as<std::string>();
188
189 // Load templates from file
190 auto include_path = std::filesystem::path{parent_path};
191 include_path /= node[option.name]["include!"].as<std::string>();
192
193 YAML::Node included_node = YAML::LoadFile(include_path.string());
194
195 option.set(
196 included_node.as<
197 std::map<std::string, clanguml::config::diagram_template>>());
198 }
199 else
200 option.set(node[option.name]
201 .as<std::map<std::string,
203}
204
205std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
206{
207 const auto diagram_type = d["type"].as<std::string>();
208
209 if (diagram_type == "class") {
210 return std::make_shared<class_diagram>(d.as<class_diagram>());
211 }
212 if (diagram_type == "sequence") {
213 return std::make_shared<sequence_diagram>(d.as<sequence_diagram>());
214 }
215 if (diagram_type == "package") {
216 return std::make_shared<package_diagram>(d.as<package_diagram>());
217 }
218 if (diagram_type == "include") {
219 return std::make_shared<include_diagram>(d.as<include_diagram>());
220 }
221
222 LOG_ERROR("Diagrams of type {} are not supported... ", diagram_type);
223
224 return {};
225}
226
227//
228// config std::filesystem::path decoder
229//
230template <> struct convert<std::filesystem::path> {
231 static bool decode(const Node &node, std::filesystem::path &rhs)
232 {
233 if (!node.IsScalar())
234 return false;
235
236 rhs = std::filesystem::path{node.as<std::string>()};
237
238 return true;
239 }
240};
241
242//
243// config access_t decoder
244//
245template <> struct convert<access_t> {
246 static bool decode(const Node &node, access_t &rhs)
247 {
248 if (node.as<std::string>() == "public")
249 rhs = access_t::kPublic;
250 else if (node.as<std::string>() == "protected")
251 rhs = access_t::kProtected;
252 else if (node.as<std::string>() == "private")
253 rhs = access_t::kPrivate;
254 else
255 return false;
256
257 return true;
258 }
259};
260
261//
262// config module_access_t decoder
263//
264template <> struct convert<module_access_t> {
265 static bool decode(const Node &node, module_access_t &rhs)
266 {
267 if (node.as<std::string>() == "public")
268 rhs = module_access_t::kPublic;
269 else if (node.as<std::string>() == "private")
270 rhs = module_access_t::kPrivate;
271 else
272 return false;
273
274 return true;
275 }
276};
277
278//
279// config context_direction_t decoder
280//
281template <> struct convert<context_direction_t> {
282 static bool decode(const Node &node, context_direction_t &rhs)
283 {
284 if (node.as<std::string>() == "inward")
285 rhs = context_direction_t::inward;
286 else if (node.as<std::string>() == "outward")
287 rhs = context_direction_t::outward;
288 else if (node.as<std::string>() == "any")
289 rhs = context_direction_t::any;
290 else
291 return false;
292
293 return true;
294 }
295};
296
297//
298// config method_type decoder
299//
300template <> struct convert<method_type> {
301 static bool decode(const Node &node, method_type &rhs)
302 {
303 const auto &val = node.as<std::string>();
304 if (val == to_string(method_type::constructor))
305 rhs = method_type::constructor;
306 else if (val == to_string(method_type::destructor))
307 rhs = method_type::destructor;
308 else if (val == to_string(method_type::assignment))
309 rhs = method_type::assignment;
310 else if (val == to_string(method_type::operator_))
311 rhs = method_type::operator_;
312 else if (val == to_string(method_type::defaulted))
313 rhs = method_type::defaulted;
314 else if (val == to_string(method_type::deleted))
315 rhs = method_type::deleted;
316 else if (val == to_string(method_type::static_))
317 rhs = method_type::static_;
318 else
319 return false;
320
321 return true;
322 }
323};
324
325//
326// config callee_type decoder
327//
328template <> struct convert<callee_type> {
329 static bool decode(const Node &node, callee_type &rhs)
330 {
331 const auto &val = node.as<std::string>();
332 if (val == to_string(callee_type::constructor))
333 rhs = callee_type::constructor;
334 else if (val == to_string(callee_type::assignment))
335 rhs = callee_type::assignment;
336 else if (val == to_string(callee_type::operator_))
337 rhs = callee_type::operator_;
338 else if (val == to_string(callee_type::defaulted))
339 rhs = callee_type::defaulted;
340 else if (val == to_string(callee_type::static_))
341 rhs = callee_type::static_;
342 else if (val == to_string(callee_type::function))
343 rhs = callee_type::function;
344 else if (val == to_string(callee_type::function_template))
345 rhs = callee_type::function_template;
346 else if (val == to_string(callee_type::method))
347 rhs = callee_type::method;
348 else if (val == to_string(callee_type::lambda))
349 rhs = callee_type::lambda;
350 else if (val == to_string(callee_type::cuda_kernel))
351 rhs = callee_type::cuda_kernel;
352 else if (val == to_string(callee_type::cuda_device))
353 rhs = callee_type::cuda_device;
354 else
355 return false;
356
357 return true;
358 }
359};
360
361//
362// config relationship_t decoder
363//
364template <> struct convert<relationship_t> {
365 static bool decode(const Node &node, relationship_t &rhs)
366 {
367 assert(node.Type() == NodeType::Scalar);
368
369 auto relationship_name = node.as<std::string>();
370 if (relationship_name == "extension" ||
371 relationship_name == "inheritance") {
372 rhs = relationship_t::kExtension;
373 }
374 else if (relationship_name == "composition") {
375 rhs = relationship_t::kComposition;
376 }
377 else if (relationship_name == "aggregation") {
378 rhs = relationship_t::kAggregation;
379 }
380 else if (relationship_name == "containment") {
381 rhs = relationship_t::kContainment;
382 }
383 else if (relationship_name == "ownership") {
384 rhs = relationship_t::kOwnership;
385 }
386 else if (relationship_name == "association") {
387 rhs = relationship_t::kAssociation;
388 }
389 else if (relationship_name == "instantiation") {
390 rhs = relationship_t::kInstantiation;
391 }
392 else if (relationship_name == "friendship") {
393 rhs = relationship_t::kFriendship;
394 }
395 else if (relationship_name == "dependency") {
396 rhs = relationship_t::kDependency;
397 }
398 else if (relationship_name == "none") {
399 rhs = relationship_t::kNone;
400 }
401 else
402 return false;
403
404 return true;
405 }
406};
407
408template <> struct convert<std::vector<source_location>> {
409 static bool decode(const Node &node, std::vector<source_location> &rhs)
410 {
411 for (auto it = node.begin(); it != node.end(); ++it) {
412 const YAML::Node &n = *it;
413 if (n["marker"]) {
414 source_location loc;
415 loc.location_type = location_t::marker;
416 loc.location = n["marker"].as<std::string>();
417 rhs.emplace_back(std::move(loc));
418 }
419 else if (n["file"] && n["line"]) {
420 source_location loc;
421 loc.location_type = location_t::fileline;
422 loc.location = n["file"].as<std::string>() + ":" +
423 n["line"].as<std::string>();
424 rhs.emplace_back(std::move(loc));
425 }
426 else if (n["function"]) {
427 source_location loc;
428 loc.location_type = location_t::function;
429 loc.location = n["function"].as<std::string>();
430 rhs.emplace_back(std::move(loc));
431 }
432 else {
433 return false;
434 }
435 }
436
437 return true;
438 }
439};
440
441template <> struct convert<plantuml> {
442 static bool decode(const Node &node, plantuml &rhs)
443 {
444 if (node["before"])
445 rhs.before = node["before"].as<decltype(rhs.before)>();
446
447 if (node["after"])
448 rhs.after = node["after"].as<decltype(rhs.after)>();
449
450 if (node["cmd"])
451 rhs.cmd = node["cmd"].as<decltype(rhs.cmd)>();
452
453 if (node["style"])
454 rhs.style = node["style"].as<decltype(rhs.style)>();
455
456 return true;
457 }
458};
459
460template <> struct convert<mermaid> {
461 static bool decode(const Node &node, mermaid &rhs)
462 {
463 if (node["before"])
464 rhs.before = node["before"].as<decltype(rhs.before)>();
465
466 if (node["after"])
467 rhs.after = node["after"].as<decltype(rhs.after)>();
468
469 if (node["cmd"])
470 rhs.cmd = node["cmd"].as<decltype(rhs.cmd)>();
471
472 return true;
473 }
474};
475
476template <> struct convert<string_or_regex> {
477 static bool decode(const Node &node, string_or_regex &rhs)
478 {
479 using namespace std::string_literals;
480 if (node.IsMap()) {
481 auto pattern = node["r"].as<std::string>();
482 auto rx = std::regex(pattern);
483 rhs = string_or_regex{std::move(rx), std::move(pattern)};
484 }
485 else {
486 rhs = string_or_regex{node.as<std::string>()};
487 }
488
489 return true;
490 }
491};
492
493template <> struct convert<context_config> {
494 static bool decode(const Node &node, context_config &rhs)
495 {
496 using namespace std::string_literals;
497 if (node.IsMap() && has_key(node, "match")) {
498 const auto &match = node["match"];
499 rhs.radius = match["radius"].as<unsigned>();
500 rhs.pattern = match["pattern"].as<string_or_regex>();
501 if (has_key(match, "direction"))
502 rhs.direction = match["direction"].as<context_direction_t>();
503 if (has_key(match, "relationships"))
504 rhs.relationships =
505 match["relationships"].as<std::vector<relationship_t>>();
506 }
507 else {
508 rhs.radius = 1;
509 rhs.pattern = node.as<string_or_regex>();
510 }
511
512 return true;
513 }
514};
515
516template <> struct convert<namespace_or_regex> {
517 static bool decode(const Node &node, namespace_or_regex &rhs)
518 {
519 using namespace std::string_literals;
520 if (node.IsMap()) {
521 auto pattern = node["r"].as<std::string>();
522 auto rx = std::regex(pattern);
523 rhs = namespace_or_regex{std::move(rx), std::move(pattern)};
524 }
525 else {
526 rhs = namespace_or_regex{node.as<std::string>()};
527 }
528
529 return true;
530 }
531};
532
533//
534// filter Yaml decoder
535//
536template <> struct convert<filter> {
537 static bool decode(const Node &node, filter &rhs)
538 {
539 if (node["anyof"]) {
540 rhs.anyof = std::make_unique<filter>(node["anyof"].as<filter>());
541 }
542
543 if (node["allof"]) {
544 rhs.allof = std::make_unique<filter>(node["allof"].as<filter>());
545 }
546
547 if (node["namespaces"]) {
548 auto namespace_list =
549 node["namespaces"].as<decltype(rhs.namespaces)>();
550 for (const auto &ns : namespace_list)
551 rhs.namespaces.push_back({ns});
552 }
553
554 if (node["modules"]) {
555 auto module_list = node["modules"].as<decltype(rhs.modules)>();
556 for (const auto &ns : module_list)
557 rhs.modules.push_back({ns});
558 }
559
560 if (node["module_access"])
561 rhs.module_access =
562 node["module_access"].as<decltype(rhs.module_access)>();
563
564 if (node["relationships"])
565 rhs.relationships =
566 node["relationships"].as<decltype(rhs.relationships)>();
567
568 if (node["elements"])
569 rhs.elements = node["elements"].as<decltype(rhs.elements)>();
570
571 if (node["element_types"])
572 rhs.element_types =
573 node["element_types"].as<decltype(rhs.element_types)>();
574
575 if (node["method_types"])
576 rhs.method_types =
577 node["method_types"].as<decltype(rhs.method_types)>();
578
579 if (node["access"])
580 rhs.access = node["access"].as<decltype(rhs.access)>();
581
582 if (node["subclasses"])
583 rhs.subclasses = node["subclasses"].as<decltype(rhs.subclasses)>();
584
585 if (node["parents"])
586 rhs.parents = node["parents"].as<decltype(rhs.parents)>();
587
588 if (node["specializations"])
589 rhs.specializations =
590 node["specializations"].as<decltype(rhs.specializations)>();
591
592 if (node["dependants"])
593 rhs.dependants = node["dependants"].as<decltype(rhs.dependants)>();
594
595 if (node["dependencies"])
596 rhs.dependencies =
597 node["dependencies"].as<decltype(rhs.dependencies)>();
598
599 if (node["context"])
600 rhs.context = node["context"].as<decltype(rhs.context)>();
601
602 if (node["paths"])
603 rhs.paths = node["paths"].as<decltype(rhs.paths)>();
604
605 if (node["callee_types"])
606 rhs.callee_types =
607 node["callee_types"].as<decltype(rhs.callee_types)>();
608
609 return true;
610 }
611};
612
613//
614// generate_links_config Yaml decoder
615//
616template <> struct convert<generate_links_config> {
617 static bool decode(const Node &node, generate_links_config &rhs)
618 {
619 if (node["link"]) {
620 if (node["link"].IsMap())
621 rhs.link = node["link"].as<decltype(rhs.link)>();
622 else
623 rhs.link.emplace(".", node["link"].as<std::string>());
624 }
625
626 if (node["tooltip"]) {
627 if (node["tooltip"].IsMap())
628 rhs.tooltip = node["tooltip"].as<decltype(rhs.tooltip)>();
629 else
630 rhs.tooltip.emplace(".", node["tooltip"].as<std::string>());
631 }
632
633 return true;
634 }
635};
636
637//
638// git_config Yaml decoder
639//
640template <> struct convert<git_config> {
641 static bool decode(const Node &node, git_config &rhs)
642 {
643 if (node["branch"])
644 rhs.branch = node["branch"].as<decltype(rhs.branch)>();
645
646 if (node["revision"])
647 rhs.revision = node["revision"].as<decltype(rhs.revision)>();
648
649 if (node["commit"])
650 rhs.commit = node["commit"].as<decltype(rhs.commit)>();
651
652 if (node["toplevel"])
653 rhs.toplevel = node["toplevel"].as<decltype(rhs.toplevel)>();
654
655 return true;
656 }
657};
658
659template <typename T> bool decode_diagram(const Node &node, T &rhs)
660{
661 // Decode options common for all diagrams
662 get_option(node, rhs.glob);
663 get_option(node, rhs.using_namespace);
664 get_option(node, rhs.using_module);
665 get_option(node, rhs.filter_mode);
666 get_option(node, rhs.include_system_headers);
667 get_option(node, rhs.include);
668 get_option(node, rhs.exclude);
669 get_option(node, rhs.puml);
670 get_option(node, rhs.mermaid);
671 get_option(node, rhs.git);
672 get_option(node, rhs.generate_links);
673 get_option(node, rhs.type_aliases);
674 get_option(node, rhs.comment_parser);
675 get_option(node, rhs.debug_mode);
676 get_option(node, rhs.generate_metadata);
677 get_option(node, rhs.title);
678 get_option(node, rhs.get_relative_to());
679
680 return true;
681}
682
683//
684// class_diagram Yaml decoder
685//
686template <> struct convert<class_diagram> {
687 static bool decode(const Node &node, class_diagram &rhs)
688 {
689 if (!decode_diagram(node, rhs))
690 return false;
691
692 get_option(node, rhs.layout);
696 get_option(node, rhs.group_methods);
697 get_option(node, rhs.member_order);
698 get_option(node, rhs.generate_packages);
699 get_option(node, rhs.package_type);
703 get_option(node, rhs.type_aliases);
704
705 get_option(node, rhs.get_relative_to());
706
709
710 return true;
711 }
712};
713
714//
715// sequence_diagram Yaml decoder
716//
717template <> struct convert<sequence_diagram> {
718 static bool decode(const Node &node, sequence_diagram &rhs)
719 {
720 if (!decode_diagram(node, rhs))
721 return false;
722
723 get_option(node, rhs.from);
724 get_option(node, rhs.from_to);
725 get_option(node, rhs.to);
734 get_option(node, rhs.type_aliases);
735
736 get_option(node, rhs.get_relative_to());
737
739
740 return true;
741 }
742};
743
744//
745// package_diagram Yaml decoder
746//
747template <> struct convert<package_diagram> {
748 static bool decode(const Node &node, package_diagram &rhs)
749 {
750 if (!decode_diagram(node, rhs))
751 return false;
752
753 get_option(node, rhs.layout);
754 get_option(node, rhs.package_type);
755
756 get_option(node, rhs.get_relative_to());
757
758 return true;
759 }
760};
761
762//
763// include_diagram Yaml decoder
764//
765template <> struct convert<include_diagram> {
766 static bool decode(const Node &node, include_diagram &rhs)
767 {
768 if (!decode_diagram(node, rhs))
769 return false;
770
771 get_option(node, rhs.layout);
773
774 get_option(node, rhs.get_relative_to());
775
776 return true;
777 }
778};
779
780//
781// layout_hint Yaml decoder
782//
783template <> struct convert<layout_hint> {
784 static bool decode(const Node &node, layout_hint &rhs)
785 {
786 assert(node.Type() == NodeType::Map);
787
788 if (node["up"]) {
789 rhs.hint = hint_t::up;
790 rhs.entity = node["up"].as<std::string>();
791 }
792 else if (node["down"]) {
793 rhs.hint = hint_t::down;
794 rhs.entity = node["down"].as<std::string>();
795 }
796 else if (node["left"]) {
797 rhs.hint = hint_t::left;
798 rhs.entity = node["left"].as<std::string>();
799 }
800 else if (node["right"]) {
801 rhs.hint = hint_t::right;
802 rhs.entity = node["right"].as<std::string>();
803 }
804 else if (node["together"]) {
805 rhs.hint = hint_t::together;
806 rhs.entity = node["together"].as<std::vector<std::string>>();
807 }
808 else if (node["row"]) {
809 rhs.hint = hint_t::row;
810 rhs.entity = node["row"].as<std::vector<std::string>>();
811 }
812 else if (node["column"]) {
813 rhs.hint = hint_t::column;
814 rhs.entity = node["column"].as<std::vector<std::string>>();
815 }
816 else
817 return false;
818
819 return true;
820 }
821};
822
823//
824// relationship_hint_t Yaml decoder
825//
826template <> struct convert<relationship_hint_t> {
827 static bool decode(const Node &node, relationship_hint_t &rhs)
828 {
829 assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
830
831 if (node.Type() == NodeType::Scalar) {
832 // This will be default relationship hint for all arguments
833 // of this template (useful for instance for tuples)
834 rhs.default_hint = node.as<relationship_t>();
835 }
836 else {
837 for (const auto &it : node) {
838 auto key = it.first.as<std::string>();
839 if (key == "default") {
840 rhs.default_hint = node["default"].as<relationship_t>();
841 }
842 else {
843 try {
844 auto index = stoul(key);
845 rhs.argument_hints[index] =
846 it.second.as<relationship_t>();
847 }
848 catch (std::exception &e) {
849 return false;
850 }
851 }
852 }
853 }
854
855 return true;
856 }
857};
858
859//
860// diagram_template Yaml decoder
861//
862template <> struct convert<diagram_template> {
863 static bool decode(const Node &node, diagram_template &rhs)
864 {
865 assert(node.Type() == NodeType::Map);
866
868 node["type"].as<std::string>());
869 rhs.jinja_template = node["template"].as<std::string>();
870 rhs.description = node["description"].as<std::string>();
871
872 return true;
873 }
874};
875
876//
877// config Yaml decoder
878//
879template <> struct convert<config> {
880 static bool decode(const Node &node, config &rhs)
881 {
882 get_option(node, rhs.glob);
883 get_option(node, rhs.using_namespace);
884 get_option(node, rhs.using_module);
885 get_option(node, rhs.filter_mode);
886 get_option(node, rhs.output_directory);
887 get_option(node, rhs.query_driver);
890 get_option(node, rhs.add_compile_flags);
893 get_option(node, rhs.puml);
894 get_option(node, rhs.mermaid);
897 get_option(node, rhs.generate_packages);
898 get_option(node, rhs.package_type);
901 get_option(node, rhs.generate_links);
903 get_option(node, rhs.git);
904 get_option(node, rhs.comment_parser);
905 get_option(node, rhs.debug_mode);
906 get_option(node, rhs.generate_metadata);
913 get_option(node, rhs.type_aliases);
914
915 rhs.base_directory.set(node["__parent_path"].as<std::string>());
916 get_option(node, rhs.get_relative_to());
917
918 get_option(node, rhs.diagram_templates);
919
920 auto diagrams = node["diagrams"];
921
922 for (auto d : diagrams) {
923 auto name = d.first.as<std::string>();
924 std::shared_ptr<clanguml::config::diagram> diagram_config{};
925 auto parent_path = node["__parent_path"].as<std::string>();
926 d.second.force_insert("__parent_path", parent_path);
927
928 diagram_config = parse_diagram_config(d.second);
929 if (diagram_config) {
930 diagram_config->name = name;
931 rhs.diagrams[name] = diagram_config;
932 }
933 else {
934 return false;
935 }
936 }
937
938 return true;
939 }
940};
941} // namespace YAML
942
943namespace clanguml::config {
944
946{
947 const auto &predefined_templates_str = get_predefined_diagram_templates();
948
949 YAML::Node predefined_templates = YAML::Load(predefined_templates_str);
950
951 if (!diagram_templates) {
952 diagram_templates.set({});
953 }
954
955 diagram_templates().merge(
956 predefined_templates.as<std::map<std::string, diagram_template>>());
957}
958
960{
961 for (auto &[name, diagram] : diagrams) {
962 diagram->inherit(*this);
963
965 }
966}
967
968namespace {
969void resolve_option_path(YAML::Node &doc, const std::string &option_name)
970{
971 std::filesystem::path relative_to_path{
972 doc["relative_to"].as<std::string>()};
973
974 relative_to_path = weakly_canonical(relative_to_path);
975
976 std::filesystem::path option_path{doc[option_name].as<std::string>()};
977
978 if (option_path.is_absolute())
979 return;
980
981 option_path = relative_to_path / option_path;
982 option_path = option_path.lexically_normal();
983 option_path.make_preferred();
984
985 doc[option_name] = option_path.string();
986}
987} // namespace
988
989config load(const std::string &config_file, bool inherit,
990 std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
991 bool validate)
992{
993 try {
994 auto schema = YAML::Load(clanguml::config::schema_str);
995 auto schema_validator = miroir::Validator<YAML::Node>(schema);
996
997 YAML::Node doc;
998 std::filesystem::path config_file_path{};
999
1000 if (config_file == "-") {
1001 std::istreambuf_iterator<char> stdin_stream_begin{std::cin};
1002 std::istreambuf_iterator<char> stdin_stream_end{};
1003 std::string stdin_stream_str{stdin_stream_begin, stdin_stream_end};
1004
1005 doc = YAML::Load(stdin_stream_str);
1006 }
1007 else {
1008 doc = YAML::LoadFile(config_file);
1009 }
1010
1011 // Store the parent path of the config_file to properly resolve
1012 // relative paths in config file
1013 if (has_key(doc, "__parent_path"))
1014 doc.remove("__parent_path");
1015 if (config_file == "-") {
1016 config_file_path = std::filesystem::current_path();
1017 doc.force_insert("__parent_path", config_file_path.string());
1018 }
1019 else {
1020 config_file_path =
1021 canonical(absolute(std::filesystem::path{config_file}));
1022 doc.force_insert(
1023 "__parent_path", config_file_path.parent_path().string());
1024 }
1025
1026 LOG_DBG("Effective config file path is {}", config_file_path.string());
1027
1028 //
1029 // If no relative_to path is specified in the config, make all paths
1030 // resolvable against the parent directory of the .clang-uml config
1031 // file, or against the $PWD if it was specified so in the command
1032 // line
1033 //
1034 if (!doc["relative_to"]) {
1035 bool paths_relative_to_config_file = true;
1036
1037 if (doc["paths_relative_to_config_file"] &&
1038 !doc["paths_relative_to_config_file"].as<bool>())
1039 paths_relative_to_config_file = false;
1040
1041 if (paths_relative_to_pwd && *paths_relative_to_pwd)
1042 paths_relative_to_config_file = false;
1043
1044 if (paths_relative_to_config_file)
1045 doc["relative_to"] = config_file_path.parent_path().string();
1046 else
1047 doc["relative_to"] = std::filesystem::current_path().string();
1048 }
1049
1050 if (no_metadata.has_value()) {
1051 doc["generate_metadata"] = !no_metadata.value();
1052 }
1053
1054 //
1055 // Resolve common path-like config options relative to `relative_to`
1056 //
1057 if (!doc["output_directory"]) {
1058 doc["output_directory"] = ".";
1059 }
1060 resolve_option_path(doc, "output_directory");
1061
1062 if (!doc["compilation_database_dir"]) {
1063 doc["compilation_database_dir"] = ".";
1064 }
1065 resolve_option_path(doc, "compilation_database_dir");
1066
1067 // If the current directory is also a git repository,
1068 // load some config values, which can be included in the
1069 // generated diagrams
1070 if (util::is_git_repository() && !doc["git"]) {
1071 YAML::Node git_config{YAML::NodeType::Map};
1072 git_config["branch"] = util::get_git_branch();
1073 git_config["revision"] = util::get_git_revision();
1074 git_config["commit"] = util::get_git_commit();
1075 git_config["toplevel"] = util::get_git_toplevel_dir();
1076
1077 doc["git"] = git_config;
1078 }
1079
1080 // Resolve diagram includes
1081 auto diagrams = doc["diagrams"];
1082
1083 assert(diagrams.Type() == YAML::NodeType::Map);
1084
1085 for (auto d : diagrams) {
1086 auto name = d.first.as<std::string>();
1087 std::shared_ptr<clanguml::config::diagram> diagram_config{};
1088 auto parent_path = doc["__parent_path"].as<std::string>();
1089
1090 if (has_key(d.second, "include!")) {
1091 auto include_path = std::filesystem::path{parent_path};
1092 include_path /= d.second["include!"].as<std::string>();
1093
1094 YAML::Node included_node =
1095 YAML::LoadFile(include_path.string());
1096
1097 diagrams[name] = included_node;
1098 }
1099 }
1100
1101 if (validate) {
1102 auto schema_errors = schema_validator.validate(doc);
1103
1104 if (!schema_errors.empty()) {
1105 // print validation errors
1106 for (const auto &err : schema_errors) {
1107 LOG_ERROR("Schema error: {}", err.description());
1108 }
1109
1110 throw YAML::Exception({}, "Invalid configuration schema");
1111 }
1112 }
1113
1114 auto d = doc.as<config>();
1115
1116 if (inherit)
1117 d.inherit();
1118
1119 d.initialize_diagram_templates();
1120
1121 return d;
1122 }
1123 catch (YAML::BadFile &e) {
1124 throw std::runtime_error(fmt::format(
1125 "Could not open config file {}: {}", config_file, e.what()));
1126 }
1127 catch (YAML::Exception &e) {
1128 throw std::runtime_error(fmt::format(
1129 "Cannot parse YAML file {}: {}", config_file, e.what()));
1130 }
1131}
1132} // namespace clanguml::config