0.6.2
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-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
19#include "config.h"
20#include "diagram_templates.h"
21#include "schema.h"
22#include "util/error.h"
23
24#define MIROIR_IMPLEMENTATION
25#define MIROIR_YAMLCPP_SPECIALIZATION
26#include <miroir/miroir.hpp>
27
28namespace clanguml::error {
29
30std::string to_string(miroir::ErrorType et)
31{
32 switch (et) {
33 case miroir::ErrorType::NodeNotFound:
34 return "NodeNotFound";
35 case miroir::ErrorType::InvalidValueType:
36 return "InvalidValueType";
37 case miroir::ErrorType::InvalidValue:
38 return "InvalidValue";
39 case miroir::ErrorType::MissingKeyWithType:
40 return "MissingKeyWithType";
41 case miroir::ErrorType::UndefinedNode:
42 return "UndefinedNode";
43 default:
44 return "";
45 }
46}
47
48void print(std::ostream &ostr, const config_schema_error &e,
49 logging::logger_type_t logger_type)
50{
51 if (logger_type == logging::logger_type_t::text) {
52 ostr << "ERROR: Invalid schema:\n";
53 for (const auto &schema_error : e.errors) {
54 std::cout << " - " << schema_error.description() << '\n';
55 }
56 }
57 else {
58 inja::json j;
59 j["valid"] = false;
60 j["errors"] = inja::json::array();
61 for (const auto &schema_error : e.errors) {
62 inja::json je;
63 je["path"] = schema_error.path;
64 je["error_type"] = to_string(schema_error.type);
65 je["description"] = schema_error.description();
66 j["errors"].emplace_back(std::move(je));
67 }
68 ostr << j.dump();
69 }
70}
71} // namespace clanguml::error
72
73namespace YAML {
105
106inline bool has_key(const YAML::Node &n, const std::string &key)
107{
108 assert(n.Type() == NodeType::Map);
109
110 return std::count_if(n.begin(), n.end(), [&key](auto &&n) {
111 return n.first.template as<std::string>() == key;
112 }) > 0;
113}
114
115template <typename T>
116void get_option(const Node &node, clanguml::config::option<T> &option)
117{
118 if (node[option.name])
119 option.set(node[option.name].template as<T>());
120
121 for (const auto &alt_name : option.alternate_names)
122 if (node[alt_name]) {
123 option.set(node[alt_name].template as<T>());
124 break;
125 }
126}
127
128template <typename T>
130 const Node &node, clanguml::config::option<T> &option, T default_override)
131{
132 get_option(node, option);
133
134 if (!option.is_declared)
135 option.set(default_override);
136}
137
138template <>
141{
142 if (node[option.name]) {
143 if (node[option.name].Type() == NodeType::Scalar)
144 option.set({node[option.name].template as<std::string>()});
145 else if (node[option.name].Type() == NodeType::Sequence)
146 option.set(
147 {node[option.name].template as<std::vector<std::string>>()[0]});
148 else
149 throw std::runtime_error("Invalid using_namespace value");
150 }
151}
152
153template <>
154void get_option<method_arguments>(
155 const Node &node, clanguml::config::option<method_arguments> &option)
156{
157 if (node[option.name]) {
158 const auto &val = node[option.name].as<std::string>();
159 if (val == "full")
160 option.set(method_arguments::full);
161 else if (val == "abbreviated")
162 option.set(method_arguments::abbreviated);
163 else if (val == "none")
164 option.set(method_arguments::none);
165 else
166 throw std::runtime_error(
167 "Invalid generate_method_arguments value: " + val);
168 }
169}
170
171template <>
172void get_option<member_order_t>(
173 const Node &node, clanguml::config::option<member_order_t> &option)
174{
175 if (node[option.name]) {
176 const auto &val = node[option.name].as<std::string>();
177 if (val == "as_is")
178 option.set(member_order_t::as_is);
179 else if (val == "lexical")
180 option.set(member_order_t::lexical);
181 else
182 throw std::runtime_error("Invalid member_order value: " + val);
183 }
184}
185
186template <>
187void get_option<package_type_t>(
188 const Node &node, clanguml::config::option<package_type_t> &option)
189{
190 if (node[option.name]) {
191 const auto &val = node[option.name].as<std::string>();
192 if (val == "namespace")
193 option.set(package_type_t::kNamespace);
194 else if (val == "directory")
195 option.set(package_type_t::kDirectory);
196 else if (val == "module")
197 option.set(package_type_t::kModule);
198 else
199 throw std::runtime_error(
200 "Invalid generate_method_arguments value: " + val);
201 }
202}
203
204template <>
205void get_option<clanguml::config::comment_parser_t>(const Node &node,
207{
208 if (node[option.name]) {
209 const auto &val = node[option.name].as<std::string>();
210 if (val == "plain")
212 else if (val == "clang")
214 else
215 throw std::runtime_error("Invalid comment_parser value: " + val);
216 }
217}
218
219template <>
220void get_option<clanguml::config::filter_mode_t>(const Node &node,
222{
223 if (node[option.name]) {
224 const auto &val = node[option.name].as<std::string>();
225 if (val == "basic")
227 else if (val == "advanced")
229 else
230 throw std::runtime_error("Invalid comment_parser value: " + val);
231 }
232}
233
234template <>
235void get_option<std::map<std::string, clanguml::config::diagram_template>>(
236 const Node &node,
238 std::map<std::string, clanguml::config::diagram_template>> &option)
239{
240 if (!node[option.name]) {
241 return;
242 }
243
244 if (node[option.name].IsMap() && node[option.name]["include!"]) {
245 auto parent_path = node["__parent_path"].as<std::string>();
246
247 // Load templates from file
248 auto include_path = std::filesystem::path{parent_path};
249 include_path /= node[option.name]["include!"].as<std::string>();
250
251 YAML::Node included_node = YAML::LoadFile(include_path.string());
252
253 option.set(
254 included_node.as<
255 std::map<std::string, clanguml::config::diagram_template>>());
256 }
257 else
258 option.set(node[option.name]
259 .as<std::map<std::string,
261}
262
263std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
264{
265 const auto diagram_type = d["type"].as<std::string>();
266
267 if (diagram_type == "class") {
268 return std::make_shared<class_diagram>(d.as<class_diagram>());
269 }
270 if (diagram_type == "sequence") {
271 return std::make_shared<sequence_diagram>(d.as<sequence_diagram>());
272 }
273 if (diagram_type == "package") {
274 return std::make_shared<package_diagram>(d.as<package_diagram>());
275 }
276 if (diagram_type == "include") {
277 return std::make_shared<include_diagram>(d.as<include_diagram>());
278 }
279
280 LOG_ERROR("Diagrams of type {} are not supported... ", diagram_type);
281
282 return {};
283}
284
285//
286// config std::filesystem::path decoder
287//
288template <> struct convert<std::filesystem::path> {
289 static bool decode(const Node &node, std::filesystem::path &rhs)
290 {
291 if (!node.IsScalar())
292 return false;
293
294 rhs = std::filesystem::path{node.as<std::string>()};
295
296 return true;
297 }
298};
299
300//
301// config access_t decoder
302//
303template <> struct convert<access_t> {
304 static bool decode(const Node &node, access_t &rhs)
305 {
306 if (node.as<std::string>() == "public")
307 rhs = access_t::kPublic;
308 else if (node.as<std::string>() == "protected")
309 rhs = access_t::kProtected;
310 else if (node.as<std::string>() == "private")
311 rhs = access_t::kPrivate;
312 else
313 return false;
314
315 return true;
316 }
317};
318
319//
320// config module_access_t decoder
321//
322template <> struct convert<module_access_t> {
323 static bool decode(const Node &node, module_access_t &rhs)
324 {
325 if (node.as<std::string>() == "public")
326 rhs = module_access_t::kPublic;
327 else if (node.as<std::string>() == "private")
328 rhs = module_access_t::kPrivate;
329 else
330 return false;
331
332 return true;
333 }
334};
335
336//
337// config context_direction_t decoder
338//
339template <> struct convert<context_direction_t> {
340 static bool decode(const Node &node, context_direction_t &rhs)
341 {
342 if (node.as<std::string>() == "inward")
343 rhs = context_direction_t::inward;
344 else if (node.as<std::string>() == "outward")
345 rhs = context_direction_t::outward;
346 else if (node.as<std::string>() == "any")
347 rhs = context_direction_t::any;
348 else
349 return false;
350
351 return true;
352 }
353};
354
355//
356// config method_type decoder
357//
358template <> struct convert<method_type> {
359 static bool decode(const Node &node, method_type &rhs)
360 {
361 const auto &val = node.as<std::string>();
362 if (val == to_string(method_type::constructor))
363 rhs = method_type::constructor;
364 else if (val == to_string(method_type::destructor))
365 rhs = method_type::destructor;
366 else if (val == to_string(method_type::assignment))
367 rhs = method_type::assignment;
368 else if (val == to_string(method_type::operator_))
369 rhs = method_type::operator_;
370 else if (val == to_string(method_type::defaulted))
371 rhs = method_type::defaulted;
372 else if (val == to_string(method_type::deleted))
373 rhs = method_type::deleted;
374 else if (val == to_string(method_type::static_))
375 rhs = method_type::static_;
376 else
377 return false;
378
379 return true;
380 }
381};
382
383//
384// config callee_type decoder
385//
386template <> struct convert<callee_type> {
387 static bool decode(const Node &node, callee_type &rhs)
388 {
389 const auto &val = node.as<std::string>();
390 if (val == to_string(callee_type::constructor))
391 rhs = callee_type::constructor;
392 else if (val == to_string(callee_type::assignment))
393 rhs = callee_type::assignment;
394 else if (val == to_string(callee_type::operator_))
395 rhs = callee_type::operator_;
396 else if (val == to_string(callee_type::defaulted))
397 rhs = callee_type::defaulted;
398 else if (val == to_string(callee_type::static_))
399 rhs = callee_type::static_;
400 else if (val == to_string(callee_type::function))
401 rhs = callee_type::function;
402 else if (val == to_string(callee_type::function_template))
403 rhs = callee_type::function_template;
404 else if (val == to_string(callee_type::method))
405 rhs = callee_type::method;
406 else if (val == to_string(callee_type::lambda))
407 rhs = callee_type::lambda;
408 else if (val == to_string(callee_type::cuda_kernel))
409 rhs = callee_type::cuda_kernel;
410 else if (val == to_string(callee_type::cuda_device))
411 rhs = callee_type::cuda_device;
412 else
413 return false;
414
415 return true;
416 }
417};
418
419//
420// config relationship_t decoder
421//
422template <> struct convert<relationship_t> {
423 static bool decode(const Node &node, relationship_t &rhs)
424 {
425 assert(node.Type() == NodeType::Scalar);
426
427 auto relationship_name = node.as<std::string>();
428 if (relationship_name == "extension" ||
429 relationship_name == "inheritance") {
430 rhs = relationship_t::kExtension;
431 }
432 else if (relationship_name == "composition") {
433 rhs = relationship_t::kComposition;
434 }
435 else if (relationship_name == "aggregation") {
436 rhs = relationship_t::kAggregation;
437 }
438 else if (relationship_name == "containment") {
439 rhs = relationship_t::kContainment;
440 }
441 else if (relationship_name == "ownership") {
442 rhs = relationship_t::kOwnership;
443 }
444 else if (relationship_name == "association") {
445 rhs = relationship_t::kAssociation;
446 }
447 else if (relationship_name == "instantiation") {
448 rhs = relationship_t::kInstantiation;
449 }
450 else if (relationship_name == "friendship") {
451 rhs = relationship_t::kFriendship;
452 }
453 else if (relationship_name == "dependency") {
454 rhs = relationship_t::kDependency;
455 }
456 else if (relationship_name == "none") {
457 rhs = relationship_t::kNone;
458 }
459 else
460 return false;
461
462 return true;
463 }
464};
465
466template <> struct convert<std::vector<source_location>> {
467 static bool decode(const Node &node, std::vector<source_location> &rhs)
468 {
469 for (auto it = node.begin(); it != node.end(); ++it) {
470 const YAML::Node &n = *it;
471 if (n["marker"]) {
472 source_location loc;
473 loc.location_type = location_t::marker;
474 loc.location = n["marker"].as<std::string>();
475 rhs.emplace_back(std::move(loc));
476 }
477 else if (n["file"] && n["line"]) {
478 source_location loc;
479 loc.location_type = location_t::fileline;
480 loc.location = n["file"].as<std::string>() + ":" +
481 n["line"].as<std::string>();
482 rhs.emplace_back(std::move(loc));
483 }
484 else if (n["function"]) {
485 source_location loc;
486 loc.location_type = location_t::function;
487 loc.location = n["function"].as<decltype(loc.location)>();
488 rhs.emplace_back(std::move(loc));
489 }
490 else {
491 return false;
492 }
493 }
494
495 return true;
496 }
497};
498
499template <> struct convert<plantuml> {
500 static bool decode(const Node &node, plantuml &rhs)
501 {
502 if (node["before"])
503 rhs.before = node["before"].as<decltype(rhs.before)>();
504
505 if (node["after"])
506 rhs.after = node["after"].as<decltype(rhs.after)>();
507
508 if (node["cmd"])
509 rhs.cmd = node["cmd"].as<decltype(rhs.cmd)>();
510
511 if (node["style"])
512 rhs.style = node["style"].as<decltype(rhs.style)>();
513
514 return true;
515 }
516};
517
518template <> struct convert<mermaid> {
519 static bool decode(const Node &node, mermaid &rhs)
520 {
521 if (node["before"])
522 rhs.before = node["before"].as<decltype(rhs.before)>();
523
524 if (node["after"])
525 rhs.after = node["after"].as<decltype(rhs.after)>();
526
527 if (node["cmd"])
528 rhs.cmd = node["cmd"].as<decltype(rhs.cmd)>();
529
530 return true;
531 }
532};
533
534template <> struct convert<graphml> {
535 static bool decode(const Node &node, graphml &rhs)
536 {
537 if (node["notes"])
538 rhs.notes = node["notes"].as<decltype(rhs.notes)>();
539
540 return true;
541 }
542};
543
544template <> struct convert<string_or_regex> {
545 static bool decode(const Node &node, string_or_regex &rhs)
546 {
547 using namespace std::string_literals;
548 if (node.IsMap()) {
549 auto pattern = node["r"].as<std::string>();
550 auto rx = std::regex(pattern);
551 rhs = string_or_regex{std::move(rx), std::move(pattern)};
552 }
553 else {
554 rhs = string_or_regex{node.as<std::string>()};
555 }
556
557 return true;
558 }
559};
560
561template <> struct convert<context_config> {
562 static bool decode(const Node &node, context_config &rhs)
563 {
564 using namespace std::string_literals;
565 if (node.IsMap() && has_key(node, "match")) {
566 const auto &match = node["match"];
567 rhs.radius = match["radius"].as<unsigned>();
568 rhs.pattern = match["pattern"].as<string_or_regex>();
569 if (has_key(match, "direction"))
570 rhs.direction = match["direction"].as<context_direction_t>();
571 if (has_key(match, "relationships"))
572 rhs.relationships =
573 match["relationships"].as<std::vector<relationship_t>>();
574 }
575 else {
576 rhs.radius = 1;
577 rhs.pattern = node.as<string_or_regex>();
578 }
579
580 return true;
581 }
582};
583
584template <> struct convert<namespace_or_regex> {
585 static bool decode(const Node &node, namespace_or_regex &rhs)
586 {
587 using namespace std::string_literals;
588 if (node.IsMap()) {
589 auto pattern = node["r"].as<std::string>();
590 auto rx = std::regex(pattern);
591 rhs = namespace_or_regex{std::move(rx), std::move(pattern)};
592 }
593 else {
594 rhs = namespace_or_regex{node.as<std::string>()};
595 }
596
597 return true;
598 }
599};
600
601template <> struct convert<element_filter_t> {
602 static bool decode(const Node &node, element_filter_t &rhs)
603 {
604 using namespace std::string_literals;
605 if (node.IsMap()) {
606 if (has_key(node, "r")) {
607 rhs.type = element_filter_t::filtered_type::any;
608 auto pattern = node["r"].as<std::string>();
609 auto rx = std::regex(pattern);
610 rhs.name = string_or_regex(std::move(rx), std::move(pattern));
611 }
612 else if (has_key(node, "type")) {
613 rhs.type = element_filter_t::filtered_type::any;
614 if (node["type"].as<std::string>() == "class")
615 rhs.type = element_filter_t::filtered_type::class_;
616 else if (node["type"].as<std::string>() == "enum")
617 rhs.type = element_filter_t::filtered_type::enum_;
618 else if (node["type"].as<std::string>() == "function")
619 rhs.type = element_filter_t::filtered_type::function;
620 else if (node["type"].as<std::string>() == "method")
621 rhs.type = element_filter_t::filtered_type::method;
622 else if (node["type"].as<std::string>() == "member")
623 rhs.type = element_filter_t::filtered_type::member;
624 else if (node["type"].as<std::string>() == "concept")
625 rhs.type = element_filter_t::filtered_type::concept_;
626 else if (node["type"].as<std::string>() == "package")
627 rhs.type = element_filter_t::filtered_type::package;
628 else if (node["type"].as<std::string>() == "function_template")
629 rhs.type =
630 element_filter_t::filtered_type::function_template;
631 else if (node["type"].as<std::string>() == "objc_method")
632 rhs.type = element_filter_t::filtered_type::objc_method;
633 else if (node["type"].as<std::string>() == "objc_member")
634 rhs.type = element_filter_t::filtered_type::objc_member;
635 else if (node["type"].as<std::string>() == "objc_protocol")
636 rhs.type = element_filter_t::filtered_type::objc_protocol;
637 else if (node["type"].as<std::string>() == "objc_category")
638 rhs.type = element_filter_t::filtered_type::objc_category;
639 else if (node["type"].as<std::string>() == "objc_interface")
640 rhs.type = element_filter_t::filtered_type::objc_interface;
641 auto name = node["name"];
642 if (name.IsMap() && has_key(name, "r")) {
643 auto pattern = name["r"].as<std::string>();
644 auto rx = std::regex(pattern);
645 rhs.name =
646 string_or_regex(std::move(rx), std::move(pattern));
647 }
648 else
649 rhs.name = name.as<std::string>();
650 }
651 }
652 else {
653 rhs.type = element_filter_t::filtered_type::any;
654 rhs.name = string_or_regex{node.as<std::string>()};
655 }
656
657 return true;
658 }
659};
660
661//
662// filter Yaml decoder
663//
664template <> struct convert<filter> {
665 static bool decode(const Node &node, filter &rhs)
666 {
667 if (node["anyof"]) {
668 rhs.anyof = std::make_unique<filter>(node["anyof"].as<filter>());
669 }
670
671 if (node["allof"]) {
672 rhs.allof = std::make_unique<filter>(node["allof"].as<filter>());
673 }
674
675 if (node["namespaces"]) {
676 auto namespace_list =
677 node["namespaces"].as<decltype(rhs.namespaces)>();
678 for (const auto &ns : namespace_list)
679 rhs.namespaces.push_back({ns});
680 }
681
682 if (node["modules"]) {
683 auto module_list = node["modules"].as<decltype(rhs.modules)>();
684 for (const auto &ns : module_list)
685 rhs.modules.push_back({ns});
686 }
687
688 if (node["module_access"])
689 rhs.module_access =
690 node["module_access"].as<decltype(rhs.module_access)>();
691
692 if (node["relationships"])
693 rhs.relationships =
694 node["relationships"].as<decltype(rhs.relationships)>();
695
696 if (node["elements"])
697 rhs.elements = node["elements"].as<decltype(rhs.elements)>();
698
699 if (node["element_types"])
700 rhs.element_types =
701 node["element_types"].as<decltype(rhs.element_types)>();
702
703 if (node["method_types"])
704 rhs.method_types =
705 node["method_types"].as<decltype(rhs.method_types)>();
706
707 if (node["access"])
708 rhs.access = node["access"].as<decltype(rhs.access)>();
709
710 if (node["subclasses"])
711 rhs.subclasses = node["subclasses"].as<decltype(rhs.subclasses)>();
712
713 if (node["parents"])
714 rhs.parents = node["parents"].as<decltype(rhs.parents)>();
715
716 if (node["specializations"])
717 rhs.specializations =
718 node["specializations"].as<decltype(rhs.specializations)>();
719
720 if (node["dependants"])
721 rhs.dependants = node["dependants"].as<decltype(rhs.dependants)>();
722
723 if (node["dependencies"])
724 rhs.dependencies =
725 node["dependencies"].as<decltype(rhs.dependencies)>();
726
727 if (node["context"])
728 rhs.context = node["context"].as<decltype(rhs.context)>();
729
730 if (node["paths"])
731 rhs.paths = node["paths"].as<decltype(rhs.paths)>();
732
733 if (node["callee_types"])
734 rhs.callee_types =
735 node["callee_types"].as<decltype(rhs.callee_types)>();
736
737 return true;
738 }
739};
740
741//
742// generate_links_config Yaml decoder
743//
744template <> struct convert<generate_links_config> {
745 static bool decode(const Node &node, generate_links_config &rhs)
746 {
747 if (node["link"]) {
748 if (node["link"].IsMap())
749 rhs.link = node["link"].as<decltype(rhs.link)>();
750 else
751 rhs.link.emplace(".", node["link"].as<std::string>());
752 }
753
754 if (node["tooltip"]) {
755 if (node["tooltip"].IsMap())
756 rhs.tooltip = node["tooltip"].as<decltype(rhs.tooltip)>();
757 else
758 rhs.tooltip.emplace(".", node["tooltip"].as<std::string>());
759 }
760
761 return true;
762 }
763};
764
765//
766// git_config Yaml decoder
767//
768template <> struct convert<git_config> {
769 static bool decode(const Node &node, git_config &rhs)
770 {
771 if (node["branch"])
772 rhs.branch = node["branch"].as<decltype(rhs.branch)>();
773
774 if (node["revision"])
775 rhs.revision = node["revision"].as<decltype(rhs.revision)>();
776
777 if (node["commit"])
778 rhs.commit = node["commit"].as<decltype(rhs.commit)>();
779
780 if (node["toplevel"])
781 rhs.toplevel = node["toplevel"].as<decltype(rhs.toplevel)>();
782
783 return true;
784 }
785};
786
787template <typename T> bool decode_diagram(const Node &node, T &rhs)
788{
789 // Decode options common for all diagrams
790 get_option(node, rhs.glob);
791 get_option(node, rhs.using_namespace);
792 get_option(node, rhs.using_module);
793 get_option(node, rhs.filter_mode);
794 get_option(node, rhs.include_system_headers);
795 get_option(node, rhs.include);
796 get_option(node, rhs.exclude);
797 get_option(node, rhs.puml);
798 get_option(node, rhs.mermaid);
799 get_option(node, rhs.graphml);
800 get_option(node, rhs.git);
801 get_option(node, rhs.generate_links);
802 get_option(node, rhs.type_aliases);
803 get_option(node, rhs.comment_parser);
804 get_option(node, rhs.debug_mode);
805 get_option(node, rhs.generate_metadata);
806 get_option(node, rhs.title);
807 get_option(node, rhs.get_relative_to());
808 get_option(node, rhs.user_data);
809
810 return true;
811}
812
813//
814// class_diagram Yaml decoder
815//
816template <> struct convert<class_diagram> {
817 static bool decode(const Node &node, class_diagram &rhs)
818 {
819 if (!decode_diagram(node, rhs))
820 return false;
821
822 get_option(node, rhs.layout);
826 get_option(node, rhs.group_methods);
827 get_option(node, rhs.member_order);
828 get_option(node, rhs.generate_packages);
829 get_option(node, rhs.package_type);
833 get_option(node, rhs.type_aliases);
834
835 get_option(node, rhs.get_relative_to());
836
839
840 return true;
841 }
842};
843
844//
845// sequence_diagram Yaml decoder
846//
847template <> struct convert<sequence_diagram> {
848 static bool decode(const Node &node, sequence_diagram &rhs)
849 {
850 if (!decode_diagram(node, rhs))
851 return false;
852
853 get_option(node, rhs.from);
854 get_option(node, rhs.from_to);
855 get_option(node, rhs.to);
868 get_option(node, rhs.type_aliases);
869
870 get_option(node, rhs.get_relative_to());
871
873
874 return true;
875 }
876};
877
878//
879// package_diagram Yaml decoder
880//
881template <> struct convert<package_diagram> {
882 static bool decode(const Node &node, package_diagram &rhs)
883 {
884 if (!decode_diagram(node, rhs))
885 return false;
886
887 get_option(node, rhs.layout);
888 get_option(node, rhs.package_type);
889
890 get_option(node, rhs.get_relative_to());
891
892 return true;
893 }
894};
895
896//
897// include_diagram Yaml decoder
898//
899template <> struct convert<include_diagram> {
900 static bool decode(const Node &node, include_diagram &rhs)
901 {
902 if (!decode_diagram(node, rhs))
903 return false;
904
905 get_option(node, rhs.layout);
907 get_option(node, rhs.generate_packages, true);
908
909 get_option(node, rhs.get_relative_to());
910
911 return true;
912 }
913};
914
915//
916// layout_hint Yaml decoder
917//
918template <> struct convert<glob_t> {
919 static bool decode(const Node &node, glob_t &rhs)
920 {
921 if (node.Type() == NodeType::Sequence) {
922 rhs.include = node.as<std::vector<string_or_regex>>();
923 return true;
924 }
925
926 if (node.Type() == NodeType::Map) {
927 if (has_key(node, "include"))
928 rhs.include =
929 node["include"].as<std::vector<string_or_regex>>();
930 if (has_key(node, "exclude"))
931 rhs.exclude =
932 node["exclude"].as<std::vector<string_or_regex>>();
933 return true;
934 }
935
936 return false;
937 }
938};
939
940//
941// layout_hint Yaml decoder
942//
943template <> struct convert<layout_hint> {
944 static bool decode(const Node &node, layout_hint &rhs)
945 {
946 assert(node.Type() == NodeType::Map);
947
948 if (node["up"]) {
949 rhs.hint = hint_t::up;
950 rhs.entity = node["up"].as<std::string>();
951 }
952 else if (node["down"]) {
953 rhs.hint = hint_t::down;
954 rhs.entity = node["down"].as<std::string>();
955 }
956 else if (node["left"]) {
957 rhs.hint = hint_t::left;
958 rhs.entity = node["left"].as<std::string>();
959 }
960 else if (node["right"]) {
961 rhs.hint = hint_t::right;
962 rhs.entity = node["right"].as<std::string>();
963 }
964 else if (node["together"]) {
965 rhs.hint = hint_t::together;
966 rhs.entity = node["together"].as<std::vector<std::string>>();
967 }
968 else if (node["row"]) {
969 rhs.hint = hint_t::row;
970 rhs.entity = node["row"].as<std::vector<std::string>>();
971 }
972 else if (node["column"]) {
973 rhs.hint = hint_t::column;
974 rhs.entity = node["column"].as<std::vector<std::string>>();
975 }
976 else
977 return false;
978
979 return true;
980 }
981};
982
983//
984// relationship_hint_t Yaml decoder
985//
986template <> struct convert<relationship_hint_t> {
987 static bool decode(const Node &node, relationship_hint_t &rhs)
988 {
989 assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
990
991 if (node.Type() == NodeType::Scalar) {
992 // This will be default relationship hint for all arguments
993 // of this template (useful for instance for tuples)
994 rhs.default_hint = node.as<relationship_t>();
995 }
996 else {
997 for (const auto &it : node) {
998 auto key = it.first.as<std::string>();
999 if (key == "default") {
1000 rhs.default_hint = node["default"].as<relationship_t>();
1001 }
1002 else {
1003 try {
1004 auto index = stoul(key);
1005 rhs.argument_hints[index] =
1006 it.second.as<relationship_t>();
1007 }
1008 catch (std::exception &e) {
1009 return false;
1010 }
1011 }
1012 }
1013 }
1014
1015 return true;
1016 }
1017};
1018
1019//
1020// diagram_template Yaml decoder
1021//
1022template <> struct convert<diagram_template> {
1023 static bool decode(const Node &node, diagram_template &rhs)
1024 {
1025 assert(node.Type() == NodeType::Map);
1026
1028 node["type"].as<std::string>());
1029 rhs.jinja_template = node["template"].as<std::string>();
1030 rhs.description = node["description"].as<std::string>();
1031
1032 return true;
1033 }
1034};
1035
1036//
1037// config Yaml decoder
1038//
1039template <> struct convert<config> {
1040 static bool decode(const Node &node, config &rhs)
1041 {
1042 get_option(node, rhs.glob);
1043 get_option(node, rhs.using_namespace);
1044 get_option(node, rhs.using_module);
1045 get_option(node, rhs.filter_mode);
1046 get_option(node, rhs.output_directory);
1047 get_option(node, rhs.query_driver);
1050 get_option(node, rhs.add_compile_flags);
1053 get_option(node, rhs.puml);
1054 get_option(node, rhs.mermaid);
1055 get_option(node, rhs.graphml);
1059 get_option(node, rhs.generate_packages);
1060 get_option(node, rhs.package_type);
1063 get_option(node, rhs.generate_links);
1065 get_option(node, rhs.git);
1066 get_option(node, rhs.comment_parser);
1067 get_option(node, rhs.debug_mode);
1068 get_option(node, rhs.generate_metadata);
1077 get_option(node, rhs.message_name_width);
1078 get_option(node, rhs.type_aliases);
1079 get_option(node, rhs.user_data);
1080
1081 rhs.base_directory.set(node["__parent_path"].as<std::string>());
1082 get_option(node, rhs.get_relative_to());
1083
1084 get_option(node, rhs.diagram_templates);
1085
1086 auto diagrams = node["diagrams"];
1087
1088 for (auto d : diagrams) {
1089 auto name = d.first.as<std::string>();
1090 std::shared_ptr<clanguml::config::diagram> diagram_config{};
1091 auto parent_path = node["__parent_path"].as<std::string>();
1092 d.second.force_insert("__parent_path", parent_path);
1093
1094 diagram_config = parse_diagram_config(d.second);
1095 if (diagram_config) {
1096 diagram_config->name = name;
1097 rhs.diagrams[name] = diagram_config;
1098 }
1099 else {
1100 return false;
1101 }
1102 }
1103
1104 return true;
1105 }
1106};
1107
1108template <> struct convert<inja::json> {
1109 static bool parse_scalar(const YAML::Node &node, inja::json &rhs)
1110 {
1111 int i{};
1112 double d{};
1113 bool b{};
1114 std::string s;
1115
1116 if (YAML::convert<int>::decode(node, i)) {
1117 rhs = i;
1118 return true;
1119 }
1120 if (YAML::convert<double>::decode(node, d)) {
1121 rhs = d;
1122 return true;
1123 }
1124 if (YAML::convert<bool>::decode(node, b)) {
1125 rhs = b;
1126 return true;
1127 }
1128 if (YAML::convert<std::string>::decode(node, s)) {
1129 rhs = s;
1130 return true;
1131 }
1132
1133 return false;
1134 }
1135
1136 static bool decode(const Node &node, inja::json &rhs)
1137 {
1138 switch (node.Type()) {
1139 case YAML::NodeType::Null:
1140 break;
1141 case YAML::NodeType::Scalar:
1142 parse_scalar(node, rhs);
1143 break;
1144 case YAML::NodeType::Sequence:
1145 for (auto &&array_element : node)
1146 rhs.emplace_back(array_element.as<inja::json>());
1147 break;
1148 case YAML::NodeType::Map:
1149 for (auto &&it : node)
1150 rhs[it.first.as<std::string>()] = it.second.as<inja::json>();
1151 break;
1152 default:
1153 break;
1154 }
1155 return true;
1156 }
1157};
1158} // namespace YAML
1159
1160namespace clanguml::config {
1161
1163{
1164 const auto &predefined_templates_str = get_predefined_diagram_templates();
1165
1166 YAML::Node predefined_templates = YAML::Load(predefined_templates_str);
1167
1168 if (!diagram_templates) {
1169 diagram_templates.set({});
1170 }
1171
1172 diagram_templates().merge(
1173 predefined_templates.as<std::map<std::string, diagram_template>>());
1174}
1175
1177{
1178 for (auto &[name, diagram] : diagrams) {
1179 diagram->inherit(*this);
1180
1182 }
1183}
1184
1185namespace {
1186void resolve_option_path(YAML::Node &doc, const std::string &option_name)
1187{
1188 std::filesystem::path relative_to_path{
1189 doc["relative_to"].as<std::string>()};
1190
1191 relative_to_path = weakly_canonical(relative_to_path);
1192
1193 std::filesystem::path option_path{doc[option_name].as<std::string>()};
1194
1195 if (option_path.is_absolute())
1196 return;
1197
1198 option_path = relative_to_path / option_path;
1199 option_path = option_path.lexically_normal();
1200 option_path.make_preferred();
1201
1202 doc[option_name] = option_path.string();
1203}
1204} // namespace
1205
1206config load(const std::string &config_file, bool inherit,
1207 std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
1208 bool validate)
1209{
1210 try {
1211 auto schema = YAML::Load(clanguml::config::schema_str);
1212 auto schema_validator = miroir::Validator<YAML::Node>(schema);
1213
1214 YAML::Node doc;
1215 std::filesystem::path config_file_path{};
1216
1217 if (config_file == "-") {
1218 std::istreambuf_iterator<char> stdin_stream_begin{std::cin};
1219 std::istreambuf_iterator<char> stdin_stream_end{};
1220 std::string stdin_stream_str{stdin_stream_begin, stdin_stream_end};
1221
1222 doc = YAML::Load(stdin_stream_str);
1223 }
1224 else {
1225 doc = YAML::LoadFile(config_file);
1226 }
1227
1228 // Store the parent path of the config_file to properly resolve
1229 // relative paths in config file
1230 if (has_key(doc, "__parent_path"))
1231 doc.remove("__parent_path");
1232 if (config_file == "-") {
1233 config_file_path = std::filesystem::current_path();
1234 doc.force_insert("__parent_path", config_file_path.string());
1235 }
1236 else {
1237 config_file_path =
1238 canonical(absolute(std::filesystem::path{config_file}));
1239 doc.force_insert(
1240 "__parent_path", config_file_path.parent_path().string());
1241 }
1242
1243 LOG_DBG("Effective config file path is {}", config_file_path.string());
1244
1245 //
1246 // If no relative_to path is specified in the config, make all paths
1247 // resolvable against the parent directory of the .clang-uml config
1248 // file, or against the $PWD if it was specified so in the command
1249 // line
1250 //
1251 if (!doc["relative_to"]) {
1252 bool paths_relative_to_config_file = true;
1253
1254 if (doc["paths_relative_to_config_file"] &&
1255 !doc["paths_relative_to_config_file"].as<bool>())
1256 paths_relative_to_config_file = false;
1257
1258 if (paths_relative_to_pwd && *paths_relative_to_pwd)
1259 paths_relative_to_config_file = false;
1260
1261 if (paths_relative_to_config_file)
1262 doc["relative_to"] = config_file_path.parent_path().string();
1263 else
1264 doc["relative_to"] = std::filesystem::current_path().string();
1265 }
1266
1267 if (no_metadata.has_value()) {
1268 doc["generate_metadata"] = !no_metadata.value();
1269 }
1270
1271 //
1272 // Resolve common path-like config options relative to `relative_to`
1273 //
1274 if (!doc["output_directory"]) {
1275 doc["output_directory"] = ".";
1276 }
1277 resolve_option_path(doc, "output_directory");
1278
1279 if (!doc["compilation_database_dir"]) {
1280 doc["compilation_database_dir"] = ".";
1281 }
1282 resolve_option_path(doc, "compilation_database_dir");
1283
1284 // If the current directory is also a git repository,
1285 // load some config values, which can be included in the
1286 // generated diagrams
1287 if (util::is_git_repository() && !doc["git"]) {
1288 YAML::Node git_config{YAML::NodeType::Map};
1289 git_config["branch"] = util::get_git_branch();
1290 git_config["revision"] = util::get_git_revision();
1291 git_config["commit"] = util::get_git_commit();
1292 git_config["toplevel"] = util::get_git_toplevel_dir();
1293
1294 doc["git"] = git_config;
1295 }
1296
1297 if (has_key(doc, "diagrams")) {
1298 auto diagrams = doc["diagrams"];
1299
1300 assert(diagrams.Type() == YAML::NodeType::Map);
1301
1302 for (auto d : diagrams) {
1303 auto name = d.first.as<std::string>();
1304 std::shared_ptr<clanguml::config::diagram> diagram_config{};
1305 auto parent_path = doc["__parent_path"].as<std::string>();
1306
1307 if (has_key(d.second, "include!")) {
1308 auto include_path = std::filesystem::path{parent_path};
1309 include_path /= d.second["include!"].as<std::string>();
1310
1311 YAML::Node included_node =
1312 YAML::LoadFile(include_path.string());
1313
1314 diagrams[name] = included_node;
1315 }
1316 }
1317 }
1318
1319 if (validate) {
1320 auto schema_errors = schema_validator.validate(doc);
1321
1322 if (!schema_errors.empty()) {
1324 std::move(schema_errors));
1325 }
1326 }
1327
1328 auto d = doc.as<config>();
1329
1330 if (inherit)
1331 d.inherit();
1332
1333 d.initialize_diagram_templates();
1334
1335 return d;
1336 }
1337 catch (YAML::BadFile &e) {
1338 throw std::runtime_error(fmt::format(
1339 "Could not open config file {}: {}", config_file, e.what()));
1340 }
1341 catch (YAML::Exception &e) {
1342 throw std::runtime_error(
1343 fmt::format("Cannot parse YAML file {} at line {}: {}", config_file,
1344 e.mark.line, e.msg));
1345 }
1346}
1347} // namespace clanguml::config