0.6.0
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);
865 get_option(node, rhs.type_aliases);
866
867 get_option(node, rhs.get_relative_to());
868
870
871 return true;
872 }
873};
874
875//
876// package_diagram Yaml decoder
877//
878template <> struct convert<package_diagram> {
879 static bool decode(const Node &node, package_diagram &rhs)
880 {
881 if (!decode_diagram(node, rhs))
882 return false;
883
884 get_option(node, rhs.layout);
885 get_option(node, rhs.package_type);
886
887 get_option(node, rhs.get_relative_to());
888
889 return true;
890 }
891};
892
893//
894// include_diagram Yaml decoder
895//
896template <> struct convert<include_diagram> {
897 static bool decode(const Node &node, include_diagram &rhs)
898 {
899 if (!decode_diagram(node, rhs))
900 return false;
901
902 get_option(node, rhs.layout);
904 get_option(node, rhs.generate_packages, true);
905
906 get_option(node, rhs.get_relative_to());
907
908 return true;
909 }
910};
911
912//
913// layout_hint Yaml decoder
914//
915template <> struct convert<glob_t> {
916 static bool decode(const Node &node, glob_t &rhs)
917 {
918 if (node.Type() == NodeType::Sequence) {
919 rhs.include = node.as<std::vector<string_or_regex>>();
920 return true;
921 }
922
923 if (node.Type() == NodeType::Map) {
924 if (has_key(node, "include"))
925 rhs.include =
926 node["include"].as<std::vector<string_or_regex>>();
927 if (has_key(node, "exclude"))
928 rhs.exclude =
929 node["exclude"].as<std::vector<string_or_regex>>();
930 return true;
931 }
932
933 return false;
934 }
935};
936
937//
938// layout_hint Yaml decoder
939//
940template <> struct convert<layout_hint> {
941 static bool decode(const Node &node, layout_hint &rhs)
942 {
943 assert(node.Type() == NodeType::Map);
944
945 if (node["up"]) {
946 rhs.hint = hint_t::up;
947 rhs.entity = node["up"].as<std::string>();
948 }
949 else if (node["down"]) {
950 rhs.hint = hint_t::down;
951 rhs.entity = node["down"].as<std::string>();
952 }
953 else if (node["left"]) {
954 rhs.hint = hint_t::left;
955 rhs.entity = node["left"].as<std::string>();
956 }
957 else if (node["right"]) {
958 rhs.hint = hint_t::right;
959 rhs.entity = node["right"].as<std::string>();
960 }
961 else if (node["together"]) {
962 rhs.hint = hint_t::together;
963 rhs.entity = node["together"].as<std::vector<std::string>>();
964 }
965 else if (node["row"]) {
966 rhs.hint = hint_t::row;
967 rhs.entity = node["row"].as<std::vector<std::string>>();
968 }
969 else if (node["column"]) {
970 rhs.hint = hint_t::column;
971 rhs.entity = node["column"].as<std::vector<std::string>>();
972 }
973 else
974 return false;
975
976 return true;
977 }
978};
979
980//
981// relationship_hint_t Yaml decoder
982//
983template <> struct convert<relationship_hint_t> {
984 static bool decode(const Node &node, relationship_hint_t &rhs)
985 {
986 assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
987
988 if (node.Type() == NodeType::Scalar) {
989 // This will be default relationship hint for all arguments
990 // of this template (useful for instance for tuples)
991 rhs.default_hint = node.as<relationship_t>();
992 }
993 else {
994 for (const auto &it : node) {
995 auto key = it.first.as<std::string>();
996 if (key == "default") {
997 rhs.default_hint = node["default"].as<relationship_t>();
998 }
999 else {
1000 try {
1001 auto index = stoul(key);
1002 rhs.argument_hints[index] =
1003 it.second.as<relationship_t>();
1004 }
1005 catch (std::exception &e) {
1006 return false;
1007 }
1008 }
1009 }
1010 }
1011
1012 return true;
1013 }
1014};
1015
1016//
1017// diagram_template Yaml decoder
1018//
1019template <> struct convert<diagram_template> {
1020 static bool decode(const Node &node, diagram_template &rhs)
1021 {
1022 assert(node.Type() == NodeType::Map);
1023
1025 node["type"].as<std::string>());
1026 rhs.jinja_template = node["template"].as<std::string>();
1027 rhs.description = node["description"].as<std::string>();
1028
1029 return true;
1030 }
1031};
1032
1033//
1034// config Yaml decoder
1035//
1036template <> struct convert<config> {
1037 static bool decode(const Node &node, config &rhs)
1038 {
1039 get_option(node, rhs.glob);
1040 get_option(node, rhs.using_namespace);
1041 get_option(node, rhs.using_module);
1042 get_option(node, rhs.filter_mode);
1043 get_option(node, rhs.output_directory);
1044 get_option(node, rhs.query_driver);
1047 get_option(node, rhs.add_compile_flags);
1050 get_option(node, rhs.puml);
1051 get_option(node, rhs.mermaid);
1052 get_option(node, rhs.graphml);
1055 get_option(node, rhs.generate_packages);
1056 get_option(node, rhs.package_type);
1059 get_option(node, rhs.generate_links);
1061 get_option(node, rhs.git);
1062 get_option(node, rhs.comment_parser);
1063 get_option(node, rhs.debug_mode);
1064 get_option(node, rhs.generate_metadata);
1072 get_option(node, rhs.type_aliases);
1073 get_option(node, rhs.user_data);
1074
1075 rhs.base_directory.set(node["__parent_path"].as<std::string>());
1076 get_option(node, rhs.get_relative_to());
1077
1078 get_option(node, rhs.diagram_templates);
1079
1080 auto diagrams = node["diagrams"];
1081
1082 for (auto d : diagrams) {
1083 auto name = d.first.as<std::string>();
1084 std::shared_ptr<clanguml::config::diagram> diagram_config{};
1085 auto parent_path = node["__parent_path"].as<std::string>();
1086 d.second.force_insert("__parent_path", parent_path);
1087
1088 diagram_config = parse_diagram_config(d.second);
1089 if (diagram_config) {
1090 diagram_config->name = name;
1091 rhs.diagrams[name] = diagram_config;
1092 }
1093 else {
1094 return false;
1095 }
1096 }
1097
1098 return true;
1099 }
1100};
1101
1102template <> struct convert<inja::json> {
1103 static bool parse_scalar(const YAML::Node &node, inja::json &rhs)
1104 {
1105 int i{};
1106 double d{};
1107 bool b{};
1108 std::string s;
1109
1110 if (YAML::convert<int>::decode(node, i)) {
1111 rhs = i;
1112 return true;
1113 }
1114 if (YAML::convert<double>::decode(node, d)) {
1115 rhs = d;
1116 return true;
1117 }
1118 if (YAML::convert<bool>::decode(node, b)) {
1119 rhs = b;
1120 return true;
1121 }
1122 if (YAML::convert<std::string>::decode(node, s)) {
1123 rhs = s;
1124 return true;
1125 }
1126
1127 return false;
1128 }
1129
1130 static bool decode(const Node &node, inja::json &rhs)
1131 {
1132 switch (node.Type()) {
1133 case YAML::NodeType::Null:
1134 break;
1135 case YAML::NodeType::Scalar:
1136 parse_scalar(node, rhs);
1137 break;
1138 case YAML::NodeType::Sequence:
1139 for (auto &&array_element : node)
1140 rhs.emplace_back(array_element.as<inja::json>());
1141 break;
1142 case YAML::NodeType::Map:
1143 for (auto &&it : node)
1144 rhs[it.first.as<std::string>()] = it.second.as<inja::json>();
1145 break;
1146 default:
1147 break;
1148 }
1149 return true;
1150 }
1151};
1152} // namespace YAML
1153
1154namespace clanguml::config {
1155
1157{
1158 const auto &predefined_templates_str = get_predefined_diagram_templates();
1159
1160 YAML::Node predefined_templates = YAML::Load(predefined_templates_str);
1161
1162 if (!diagram_templates) {
1163 diagram_templates.set({});
1164 }
1165
1166 diagram_templates().merge(
1167 predefined_templates.as<std::map<std::string, diagram_template>>());
1168}
1169
1171{
1172 for (auto &[name, diagram] : diagrams) {
1173 diagram->inherit(*this);
1174
1176 }
1177}
1178
1179namespace {
1180void resolve_option_path(YAML::Node &doc, const std::string &option_name)
1181{
1182 std::filesystem::path relative_to_path{
1183 doc["relative_to"].as<std::string>()};
1184
1185 relative_to_path = weakly_canonical(relative_to_path);
1186
1187 std::filesystem::path option_path{doc[option_name].as<std::string>()};
1188
1189 if (option_path.is_absolute())
1190 return;
1191
1192 option_path = relative_to_path / option_path;
1193 option_path = option_path.lexically_normal();
1194 option_path.make_preferred();
1195
1196 doc[option_name] = option_path.string();
1197}
1198} // namespace
1199
1200config load(const std::string &config_file, bool inherit,
1201 std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
1202 bool validate)
1203{
1204 try {
1205 auto schema = YAML::Load(clanguml::config::schema_str);
1206 auto schema_validator = miroir::Validator<YAML::Node>(schema);
1207
1208 YAML::Node doc;
1209 std::filesystem::path config_file_path{};
1210
1211 if (config_file == "-") {
1212 std::istreambuf_iterator<char> stdin_stream_begin{std::cin};
1213 std::istreambuf_iterator<char> stdin_stream_end{};
1214 std::string stdin_stream_str{stdin_stream_begin, stdin_stream_end};
1215
1216 doc = YAML::Load(stdin_stream_str);
1217 }
1218 else {
1219 doc = YAML::LoadFile(config_file);
1220 }
1221
1222 // Store the parent path of the config_file to properly resolve
1223 // relative paths in config file
1224 if (has_key(doc, "__parent_path"))
1225 doc.remove("__parent_path");
1226 if (config_file == "-") {
1227 config_file_path = std::filesystem::current_path();
1228 doc.force_insert("__parent_path", config_file_path.string());
1229 }
1230 else {
1231 config_file_path =
1232 canonical(absolute(std::filesystem::path{config_file}));
1233 doc.force_insert(
1234 "__parent_path", config_file_path.parent_path().string());
1235 }
1236
1237 LOG_DBG("Effective config file path is {}", config_file_path.string());
1238
1239 //
1240 // If no relative_to path is specified in the config, make all paths
1241 // resolvable against the parent directory of the .clang-uml config
1242 // file, or against the $PWD if it was specified so in the command
1243 // line
1244 //
1245 if (!doc["relative_to"]) {
1246 bool paths_relative_to_config_file = true;
1247
1248 if (doc["paths_relative_to_config_file"] &&
1249 !doc["paths_relative_to_config_file"].as<bool>())
1250 paths_relative_to_config_file = false;
1251
1252 if (paths_relative_to_pwd && *paths_relative_to_pwd)
1253 paths_relative_to_config_file = false;
1254
1255 if (paths_relative_to_config_file)
1256 doc["relative_to"] = config_file_path.parent_path().string();
1257 else
1258 doc["relative_to"] = std::filesystem::current_path().string();
1259 }
1260
1261 if (no_metadata.has_value()) {
1262 doc["generate_metadata"] = !no_metadata.value();
1263 }
1264
1265 //
1266 // Resolve common path-like config options relative to `relative_to`
1267 //
1268 if (!doc["output_directory"]) {
1269 doc["output_directory"] = ".";
1270 }
1271 resolve_option_path(doc, "output_directory");
1272
1273 if (!doc["compilation_database_dir"]) {
1274 doc["compilation_database_dir"] = ".";
1275 }
1276 resolve_option_path(doc, "compilation_database_dir");
1277
1278 // If the current directory is also a git repository,
1279 // load some config values, which can be included in the
1280 // generated diagrams
1281 if (util::is_git_repository() && !doc["git"]) {
1282 YAML::Node git_config{YAML::NodeType::Map};
1283 git_config["branch"] = util::get_git_branch();
1284 git_config["revision"] = util::get_git_revision();
1285 git_config["commit"] = util::get_git_commit();
1286 git_config["toplevel"] = util::get_git_toplevel_dir();
1287
1288 doc["git"] = git_config;
1289 }
1290
1291 if (has_key(doc, "diagrams")) {
1292 auto diagrams = doc["diagrams"];
1293
1294 assert(diagrams.Type() == YAML::NodeType::Map);
1295
1296 for (auto d : diagrams) {
1297 auto name = d.first.as<std::string>();
1298 std::shared_ptr<clanguml::config::diagram> diagram_config{};
1299 auto parent_path = doc["__parent_path"].as<std::string>();
1300
1301 if (has_key(d.second, "include!")) {
1302 auto include_path = std::filesystem::path{parent_path};
1303 include_path /= d.second["include!"].as<std::string>();
1304
1305 YAML::Node included_node =
1306 YAML::LoadFile(include_path.string());
1307
1308 diagrams[name] = included_node;
1309 }
1310 }
1311 }
1312
1313 if (validate) {
1314 auto schema_errors = schema_validator.validate(doc);
1315
1316 if (!schema_errors.empty()) {
1318 std::move(schema_errors));
1319 }
1320 }
1321
1322 auto d = doc.as<config>();
1323
1324 if (inherit)
1325 d.inherit();
1326
1327 d.initialize_diagram_templates();
1328
1329 return d;
1330 }
1331 catch (YAML::BadFile &e) {
1332 throw std::runtime_error(fmt::format(
1333 "Could not open config file {}: {}", config_file, e.what()));
1334 }
1335 catch (YAML::Exception &e) {
1336 throw std::runtime_error(
1337 fmt::format("Cannot parse YAML file {} at line {}: {}", config_file,
1338 e.mark.line, e.msg));
1339 }
1340}
1341} // namespace clanguml::config