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