24#define MIROIR_IMPLEMENTATION
25#define MIROIR_YAMLCPP_SPECIALIZATION
26#include <miroir/miroir.hpp>
58inline bool has_key(
const YAML::Node &n,
const std::string &key)
60 assert(n.Type() == NodeType::Map);
62 return std::count_if(n.begin(), n.end(), [&key](
auto &&n) {
63 return n.first.template as<std::string>() == key;
70 if (node[option.
name])
71 option.
set(node[option.
name].template as<T>());
75 option.
set(node[alt_name].
template as<T>());
84 if (node[option.
name]) {
85 if (node[option.
name].Type() == NodeType::Scalar)
86 option.
set({node[option.
name].template as<std::string>()});
87 else if (node[option.
name].Type() == NodeType::Sequence)
89 {node[option.
name].template as<std::vector<std::string>>()[0]});
91 throw std::runtime_error(
"Invalid using_namespace value");
96void get_option<method_arguments>(
99 if (node[option.
name]) {
100 const auto &val = node[option.
name].as<std::string>();
102 option.
set(method_arguments::full);
103 else if (val ==
"abbreviated")
104 option.
set(method_arguments::abbreviated);
105 else if (val ==
"none")
106 option.
set(method_arguments::none);
108 throw std::runtime_error(
109 "Invalid generate_method_arguments value: " + val);
114void get_option<member_order_t>(
117 if (node[option.
name]) {
118 const auto &val = node[option.
name].as<std::string>();
120 option.
set(member_order_t::as_is);
121 else if (val ==
"lexical")
122 option.
set(member_order_t::lexical);
124 throw std::runtime_error(
"Invalid member_order value: " + val);
129void get_option<package_type_t>(
132 if (node[option.
name]) {
133 const auto &val = node[option.
name].as<std::string>();
134 if (val ==
"namespace")
135 option.
set(package_type_t::kNamespace);
136 else if (val ==
"directory")
137 option.
set(package_type_t::kDirectory);
138 else if (val ==
"module")
139 option.
set(package_type_t::kModule);
141 throw std::runtime_error(
142 "Invalid generate_method_arguments value: " + val);
147void get_option<clanguml::config::comment_parser_t>(
const Node &node,
150 if (node[option.
name]) {
151 const auto &val = node[option.
name].as<std::string>();
154 else if (val ==
"clang")
157 throw std::runtime_error(
"Invalid comment_parser value: " + val);
162void get_option<clanguml::config::filter_mode_t>(
const Node &node,
165 if (node[option.
name]) {
166 const auto &val = node[option.
name].as<std::string>();
169 else if (val ==
"advanced")
172 throw std::runtime_error(
"Invalid comment_parser value: " + val);
177void get_option<std::map<std::string, clanguml::config::diagram_template>>(
180 std::map<std::string, clanguml::config::diagram_template>> &option)
182 if (!node[option.name]) {
186 if (node[option.name].IsMap() && node[option.name][
"include!"]) {
187 auto parent_path = node[
"__parent_path"].as<std::string>();
190 auto include_path = std::filesystem::path{parent_path};
191 include_path /= node[option.name][
"include!"].as<std::string>();
193 YAML::Node included_node = YAML::LoadFile(include_path.string());
197 std::map<std::string, clanguml::config::diagram_template>>());
200 option.set(node[option.name]
201 .as<std::map<std::string,
207 const auto diagram_type = d[
"type"].as<std::string>();
209 if (diagram_type ==
"class") {
210 return std::make_shared<class_diagram>(d.as<
class_diagram>());
212 if (diagram_type ==
"sequence") {
215 if (diagram_type ==
"package") {
218 if (diagram_type ==
"include") {
222 LOG_ERROR(
"Diagrams of type {} are not supported... ", diagram_type);
230template <>
struct convert<
std::filesystem::path> {
231 static bool decode(
const Node &node, std::filesystem::path &rhs)
233 if (!node.IsScalar())
236 rhs = std::filesystem::path{node.as<std::string>()};
245template <>
struct convert<access_t> {
246 static bool decode(
const Node &node, access_t &rhs)
248 if (node.as<std::string>() ==
"public")
249 rhs = access_t::kPublic;
250 else if (node.as<std::string>() ==
"protected")
251 rhs = access_t::kProtected;
252 else if (node.as<std::string>() ==
"private")
253 rhs = access_t::kPrivate;
264template <>
struct convert<module_access_t> {
265 static bool decode(
const Node &node, module_access_t &rhs)
267 if (node.as<std::string>() ==
"public")
268 rhs = module_access_t::kPublic;
269 else if (node.as<std::string>() ==
"private")
270 rhs = module_access_t::kPrivate;
281template <>
struct convert<context_direction_t> {
282 static bool decode(
const Node &node, context_direction_t &rhs)
284 if (node.as<std::string>() ==
"inward")
285 rhs = context_direction_t::inward;
286 else if (node.as<std::string>() ==
"outward")
287 rhs = context_direction_t::outward;
288 else if (node.as<std::string>() ==
"any")
289 rhs = context_direction_t::any;
300template <>
struct convert<method_type> {
301 static bool decode(
const Node &node, method_type &rhs)
303 const auto &val = node.as<std::string>();
304 if (val == to_string(method_type::constructor))
305 rhs = method_type::constructor;
306 else if (val == to_string(method_type::destructor))
307 rhs = method_type::destructor;
308 else if (val == to_string(method_type::assignment))
309 rhs = method_type::assignment;
310 else if (val == to_string(method_type::operator_))
311 rhs = method_type::operator_;
312 else if (val == to_string(method_type::defaulted))
313 rhs = method_type::defaulted;
314 else if (val == to_string(method_type::deleted))
315 rhs = method_type::deleted;
316 else if (val == to_string(method_type::static_))
317 rhs = method_type::static_;
328template <>
struct convert<callee_type> {
329 static bool decode(
const Node &node, callee_type &rhs)
331 const auto &val = node.as<std::string>();
332 if (val == to_string(callee_type::constructor))
333 rhs = callee_type::constructor;
334 else if (val == to_string(callee_type::assignment))
335 rhs = callee_type::assignment;
336 else if (val == to_string(callee_type::operator_))
337 rhs = callee_type::operator_;
338 else if (val == to_string(callee_type::defaulted))
339 rhs = callee_type::defaulted;
340 else if (val == to_string(callee_type::static_))
341 rhs = callee_type::static_;
342 else if (val == to_string(callee_type::function))
343 rhs = callee_type::function;
344 else if (val == to_string(callee_type::function_template))
345 rhs = callee_type::function_template;
346 else if (val == to_string(callee_type::method))
347 rhs = callee_type::method;
348 else if (val == to_string(callee_type::lambda))
349 rhs = callee_type::lambda;
350 else if (val == to_string(callee_type::cuda_kernel))
351 rhs = callee_type::cuda_kernel;
352 else if (val == to_string(callee_type::cuda_device))
353 rhs = callee_type::cuda_device;
364template <>
struct convert<relationship_t> {
365 static bool decode(
const Node &node, relationship_t &rhs)
367 assert(node.Type() == NodeType::Scalar);
369 auto relationship_name = node.as<std::string>();
370 if (relationship_name ==
"extension" ||
371 relationship_name ==
"inheritance") {
372 rhs = relationship_t::kExtension;
374 else if (relationship_name ==
"composition") {
375 rhs = relationship_t::kComposition;
377 else if (relationship_name ==
"aggregation") {
378 rhs = relationship_t::kAggregation;
380 else if (relationship_name ==
"containment") {
381 rhs = relationship_t::kContainment;
383 else if (relationship_name ==
"ownership") {
384 rhs = relationship_t::kOwnership;
386 else if (relationship_name ==
"association") {
387 rhs = relationship_t::kAssociation;
389 else if (relationship_name ==
"instantiation") {
390 rhs = relationship_t::kInstantiation;
392 else if (relationship_name ==
"friendship") {
393 rhs = relationship_t::kFriendship;
395 else if (relationship_name ==
"dependency") {
396 rhs = relationship_t::kDependency;
398 else if (relationship_name ==
"none") {
399 rhs = relationship_t::kNone;
408template <>
struct convert<
std::vector<source_location>> {
409 static bool decode(
const Node &node, std::vector<source_location> &rhs)
411 for (
auto it = node.begin(); it != node.end(); ++it) {
412 const YAML::Node &n = *it;
416 loc.
location = n[
"marker"].as<std::string>();
417 rhs.emplace_back(std::move(loc));
419 else if (n[
"file"] && n[
"line"]) {
422 loc.
location = n[
"file"].as<std::string>() +
":" +
423 n[
"line"].as<std::string>();
424 rhs.emplace_back(std::move(loc));
426 else if (n[
"function"]) {
429 loc.
location = n[
"function"].as<std::string>();
430 rhs.emplace_back(std::move(loc));
448 rhs.
after = node[
"after"].as<
decltype(rhs.
after)>();
451 rhs.
cmd = node[
"cmd"].as<
decltype(rhs.
cmd)>();
454 rhs.
style = node[
"style"].as<
decltype(rhs.
style)>();
467 rhs.
after = node[
"after"].as<
decltype(rhs.
after)>();
470 rhs.
cmd = node[
"cmd"].as<
decltype(rhs.
cmd)>();
476template <>
struct convert<string_or_regex> {
477 static bool decode(
const Node &node, string_or_regex &rhs)
479 using namespace std::string_literals;
481 auto pattern = node[
"r"].as<std::string>();
482 auto rx = std::regex(pattern);
483 rhs = string_or_regex{std::move(rx), std::move(pattern)};
486 rhs = string_or_regex{node.as<std::string>()};
496 using namespace std::string_literals;
497 if (node.IsMap() &&
has_key(node,
"match")) {
498 const auto &match = node[
"match"];
499 rhs.
radius = match[
"radius"].as<
unsigned>();
500 rhs.
pattern = match[
"pattern"].as<string_or_regex>();
501 if (
has_key(match,
"direction"))
502 rhs.
direction = match[
"direction"].as<context_direction_t>();
503 if (
has_key(match,
"relationships"))
505 match[
"relationships"].as<std::vector<relationship_t>>();
509 rhs.
pattern = node.as<string_or_regex>();
516template <>
struct convert<namespace_or_regex> {
517 static bool decode(
const Node &node, namespace_or_regex &rhs)
519 using namespace std::string_literals;
521 auto pattern = node[
"r"].as<std::string>();
522 auto rx = std::regex(pattern);
523 rhs = namespace_or_regex{std::move(rx), std::move(pattern)};
526 rhs = namespace_or_regex{node.as<std::string>()};
540 rhs.
anyof = std::make_unique<filter>(node[
"anyof"].as<filter>());
544 rhs.
allof = std::make_unique<filter>(node[
"allof"].as<filter>());
547 if (node[
"namespaces"]) {
548 auto namespace_list =
549 node[
"namespaces"].as<
decltype(rhs.
namespaces)>();
550 for (
const auto &ns : namespace_list)
554 if (node[
"modules"]) {
555 auto module_list = node[
"modules"].as<
decltype(rhs.modules)>();
556 for (
const auto &ns : module_list)
557 rhs.modules.push_back({ns});
560 if (node[
"module_access"])
562 node[
"module_access"].as<
decltype(rhs.module_access)>();
564 if (node[
"relationships"])
566 node[
"relationships"].as<
decltype(rhs.relationships)>();
568 if (node[
"elements"])
569 rhs.elements = node[
"elements"].as<
decltype(rhs.elements)>();
571 if (node[
"element_types"])
573 node[
"element_types"].as<
decltype(rhs.element_types)>();
575 if (node[
"method_types"])
577 node[
"method_types"].as<
decltype(rhs.method_types)>();
580 rhs.access = node[
"access"].as<
decltype(rhs.access)>();
582 if (node[
"subclasses"])
583 rhs.subclasses = node[
"subclasses"].as<
decltype(rhs.subclasses)>();
586 rhs.parents = node[
"parents"].as<
decltype(rhs.parents)>();
588 if (node[
"specializations"])
589 rhs.specializations =
590 node[
"specializations"].as<
decltype(rhs.specializations)>();
592 if (node[
"dependants"])
593 rhs.dependants = node[
"dependants"].as<
decltype(rhs.dependants)>();
595 if (node[
"dependencies"])
597 node[
"dependencies"].as<
decltype(rhs.dependencies)>();
600 rhs.context = node[
"context"].as<
decltype(rhs.context)>();
603 rhs.paths = node[
"paths"].as<
decltype(rhs.paths)>();
605 if (node[
"callee_types"])
607 node[
"callee_types"].as<
decltype(rhs.callee_types)>();
620 if (node[
"link"].IsMap())
621 rhs.
link = node[
"link"].as<
decltype(rhs.
link)>();
623 rhs.
link.emplace(
".", node[
"link"].as<std::string>());
626 if (node[
"tooltip"]) {
627 if (node[
"tooltip"].IsMap())
630 rhs.
tooltip.emplace(
".", node[
"tooltip"].as<std::string>());
646 if (node[
"revision"])
652 if (node[
"toplevel"])
786 assert(node.Type() == NodeType::Map);
789 rhs.
hint = hint_t::up;
790 rhs.
entity = node[
"up"].as<std::string>();
792 else if (node[
"down"]) {
793 rhs.
hint = hint_t::down;
794 rhs.
entity = node[
"down"].as<std::string>();
796 else if (node[
"left"]) {
797 rhs.
hint = hint_t::left;
798 rhs.
entity = node[
"left"].as<std::string>();
800 else if (node[
"right"]) {
801 rhs.
hint = hint_t::right;
802 rhs.
entity = node[
"right"].as<std::string>();
804 else if (node[
"together"]) {
805 rhs.
hint = hint_t::together;
806 rhs.
entity = node[
"together"].as<std::vector<std::string>>();
808 else if (node[
"row"]) {
809 rhs.
hint = hint_t::row;
810 rhs.
entity = node[
"row"].as<std::vector<std::string>>();
812 else if (node[
"column"]) {
813 rhs.
hint = hint_t::column;
814 rhs.
entity = node[
"column"].as<std::vector<std::string>>();
829 assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
831 if (node.Type() == NodeType::Scalar) {
837 for (
const auto &it : node) {
838 auto key = it.first.as<std::string>();
839 if (key ==
"default") {
840 rhs.
default_hint = node[
"default"].as<relationship_t>();
844 auto index = stoul(key);
846 it.second.as<relationship_t>();
848 catch (std::exception &e) {
865 assert(node.Type() == NodeType::Map);
868 node[
"type"].as<std::string>());
870 rhs.
description = node[
"description"].as<std::string>();
920 auto diagrams = node[
"diagrams"];
922 for (
auto d : diagrams) {
923 auto name = d.first.as<std::string>();
924 std::shared_ptr<clanguml::config::diagram> diagram_config{};
925 auto parent_path = node[
"__parent_path"].as<std::string>();
926 d.second.force_insert(
"__parent_path", parent_path);
929 if (diagram_config) {
930 diagram_config->
name = name;
931 rhs.
diagrams[name] = diagram_config;
949 YAML::Node predefined_templates = YAML::Load(predefined_templates_str);
956 predefined_templates.as<std::map<std::string, diagram_template>>());
969void resolve_option_path(YAML::Node &doc,
const std::string &option_name)
971 std::filesystem::path relative_to_path{
972 doc[
"relative_to"].as<std::string>()};
974 relative_to_path = weakly_canonical(relative_to_path);
976 std::filesystem::path option_path{doc[option_name].as<std::string>()};
978 if (option_path.is_absolute())
981 option_path = relative_to_path / option_path;
982 option_path = option_path.lexically_normal();
983 option_path.make_preferred();
985 doc[option_name] = option_path.string();
990 std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
995 auto schema_validator = miroir::Validator<YAML::Node>(schema);
998 std::filesystem::path config_file_path{};
1000 if (config_file ==
"-") {
1001 std::istreambuf_iterator<char> stdin_stream_begin{std::cin};
1002 std::istreambuf_iterator<char> stdin_stream_end{};
1003 std::string stdin_stream_str{stdin_stream_begin, stdin_stream_end};
1005 doc = YAML::Load(stdin_stream_str);
1008 doc = YAML::LoadFile(config_file);
1013 if (has_key(doc,
"__parent_path"))
1014 doc.remove(
"__parent_path");
1015 if (config_file ==
"-") {
1016 config_file_path = std::filesystem::current_path();
1017 doc.force_insert(
"__parent_path", config_file_path.string());
1021 canonical(absolute(std::filesystem::path{config_file}));
1023 "__parent_path", config_file_path.parent_path().string());
1026 LOG_DBG(
"Effective config file path is {}", config_file_path.string());
1034 if (!doc[
"relative_to"]) {
1035 bool paths_relative_to_config_file =
true;
1037 if (doc[
"paths_relative_to_config_file"] &&
1038 !doc[
"paths_relative_to_config_file"].as<bool>())
1039 paths_relative_to_config_file =
false;
1041 if (paths_relative_to_pwd && *paths_relative_to_pwd)
1042 paths_relative_to_config_file =
false;
1044 if (paths_relative_to_config_file)
1045 doc[
"relative_to"] = config_file_path.parent_path().string();
1047 doc[
"relative_to"] = std::filesystem::current_path().string();
1050 if (no_metadata.has_value()) {
1051 doc[
"generate_metadata"] = !no_metadata.value();
1057 if (!doc[
"output_directory"]) {
1058 doc[
"output_directory"] =
".";
1060 resolve_option_path(doc,
"output_directory");
1062 if (!doc[
"compilation_database_dir"]) {
1063 doc[
"compilation_database_dir"] =
".";
1065 resolve_option_path(doc,
"compilation_database_dir");
1081 auto diagrams = doc[
"diagrams"];
1083 assert(diagrams.Type() == YAML::NodeType::Map);
1085 for (
auto d : diagrams) {
1086 auto name = d.first.as<std::string>();
1087 std::shared_ptr<clanguml::config::diagram> diagram_config{};
1088 auto parent_path = doc[
"__parent_path"].as<std::string>();
1090 if (has_key(d.second,
"include!")) {
1091 auto include_path = std::filesystem::path{parent_path};
1092 include_path /= d.second[
"include!"].as<std::string>();
1094 YAML::Node included_node =
1095 YAML::LoadFile(include_path.string());
1097 diagrams[name] = included_node;
1102 auto schema_errors = schema_validator.validate(doc);
1104 if (!schema_errors.empty()) {
1106 for (
const auto &err : schema_errors) {
1107 LOG_ERROR(
"Schema error: {}", err.description());
1110 throw YAML::Exception({},
"Invalid configuration schema");
1114 auto d = doc.as<
config>();
1119 d.initialize_diagram_templates();
1123 catch (YAML::BadFile &e) {
1124 throw std::runtime_error(fmt::format(
1125 "Could not open config file {}: {}", config_file, e.what()));
1127 catch (YAML::Exception &e) {
1128 throw std::runtime_error(fmt::format(
1129 "Cannot parse YAML file {}: {}", config_file, e.what()));