24#define MIROIR_IMPLEMENTATION
25#define MIROIR_YAMLCPP_SPECIALIZATION
26#include <miroir/miroir.hpp>
30std::string to_string(miroir::ErrorType 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";
49 logging::logger_type_t logger_type)
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';
60 j[
"errors"] = inja::json::array();
61 for (
const auto &schema_error : e.
errors) {
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));
106inline bool has_key(
const YAML::Node &n,
const std::string &key)
108 assert(n.Type() == NodeType::Map);
110 return std::count_if(n.begin(), n.end(), [&key](
auto &&n) {
111 return n.first.template as<std::string>() == key;
118 if (node[option.
name])
119 option.
set(node[option.
name].template as<T>());
122 if (node[alt_name]) {
123 option.
set(node[alt_name].
template as<T>());
135 option.
set(default_override);
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)
147 {node[option.
name].template as<std::vector<std::string>>()[0]});
149 throw std::runtime_error(
"Invalid using_namespace value");
154void get_option<method_arguments>(
157 if (node[option.
name]) {
158 const auto &val = node[option.
name].as<std::string>();
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);
166 throw std::runtime_error(
167 "Invalid generate_method_arguments value: " + val);
172void get_option<member_order_t>(
175 if (node[option.
name]) {
176 const auto &val = node[option.
name].as<std::string>();
178 option.
set(member_order_t::as_is);
179 else if (val ==
"lexical")
180 option.
set(member_order_t::lexical);
182 throw std::runtime_error(
"Invalid member_order value: " + val);
187void get_option<package_type_t>(
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);
199 throw std::runtime_error(
200 "Invalid generate_method_arguments value: " + val);
205void get_option<clanguml::config::comment_parser_t>(
const Node &node,
208 if (node[option.
name]) {
209 const auto &val = node[option.
name].as<std::string>();
212 else if (val ==
"clang")
215 throw std::runtime_error(
"Invalid comment_parser value: " + val);
220void get_option<clanguml::config::filter_mode_t>(
const Node &node,
223 if (node[option.
name]) {
224 const auto &val = node[option.
name].as<std::string>();
227 else if (val ==
"advanced")
230 throw std::runtime_error(
"Invalid comment_parser value: " + val);
235void get_option<std::map<std::string, clanguml::config::diagram_template>>(
238 std::map<std::string, clanguml::config::diagram_template>> &option)
240 if (!node[option.name]) {
244 if (node[option.name].IsMap() && node[option.name][
"include!"]) {
245 auto parent_path = node[
"__parent_path"].as<std::string>();
248 auto include_path = std::filesystem::path{parent_path};
249 include_path /= node[option.name][
"include!"].as<std::string>();
251 YAML::Node included_node = YAML::LoadFile(include_path.string());
255 std::map<std::string, clanguml::config::diagram_template>>());
258 option.set(node[option.name]
259 .as<std::map<std::string,
265 const auto diagram_type = d[
"type"].as<std::string>();
267 if (diagram_type ==
"class") {
268 return std::make_shared<class_diagram>(d.as<
class_diagram>());
270 if (diagram_type ==
"sequence") {
273 if (diagram_type ==
"package") {
276 if (diagram_type ==
"include") {
280 LOG_ERROR(
"Diagrams of type {} are not supported... ", diagram_type);
288template <>
struct convert<
std::filesystem::path> {
289 static bool decode(
const Node &node, std::filesystem::path &rhs)
291 if (!node.IsScalar())
294 rhs = std::filesystem::path{node.as<std::string>()};
303template <>
struct convert<access_t> {
304 static bool decode(
const Node &node, access_t &rhs)
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;
322template <>
struct convert<module_access_t> {
323 static bool decode(
const Node &node, module_access_t &rhs)
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;
339template <>
struct convert<context_direction_t> {
340 static bool decode(
const Node &node, context_direction_t &rhs)
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;
358template <>
struct convert<method_type> {
359 static bool decode(
const Node &node, method_type &rhs)
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_;
386template <>
struct convert<callee_type> {
387 static bool decode(
const Node &node, callee_type &rhs)
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;
422template <>
struct convert<relationship_t> {
423 static bool decode(
const Node &node, relationship_t &rhs)
425 assert(node.Type() == NodeType::Scalar);
427 auto relationship_name = node.as<std::string>();
428 if (relationship_name ==
"extension" ||
429 relationship_name ==
"inheritance") {
430 rhs = relationship_t::kExtension;
432 else if (relationship_name ==
"composition") {
433 rhs = relationship_t::kComposition;
435 else if (relationship_name ==
"aggregation") {
436 rhs = relationship_t::kAggregation;
438 else if (relationship_name ==
"containment") {
439 rhs = relationship_t::kContainment;
441 else if (relationship_name ==
"ownership") {
442 rhs = relationship_t::kOwnership;
444 else if (relationship_name ==
"association") {
445 rhs = relationship_t::kAssociation;
447 else if (relationship_name ==
"instantiation") {
448 rhs = relationship_t::kInstantiation;
450 else if (relationship_name ==
"friendship") {
451 rhs = relationship_t::kFriendship;
453 else if (relationship_name ==
"dependency") {
454 rhs = relationship_t::kDependency;
456 else if (relationship_name ==
"none") {
457 rhs = relationship_t::kNone;
466template <>
struct convert<
std::vector<source_location>> {
467 static bool decode(
const Node &node, std::vector<source_location> &rhs)
469 for (
auto it = node.begin(); it != node.end(); ++it) {
470 const YAML::Node &n = *it;
474 loc.
location = n[
"marker"].as<std::string>();
475 rhs.emplace_back(std::move(loc));
477 else if (n[
"file"] && n[
"line"]) {
480 loc.
location = n[
"file"].as<std::string>() +
":" +
481 n[
"line"].as<std::string>();
482 rhs.emplace_back(std::move(loc));
484 else if (n[
"function"]) {
488 rhs.emplace_back(std::move(loc));
506 rhs.
after = node[
"after"].as<
decltype(rhs.
after)>();
509 rhs.
cmd = node[
"cmd"].as<
decltype(rhs.
cmd)>();
512 rhs.
style = node[
"style"].as<
decltype(rhs.
style)>();
525 rhs.
after = node[
"after"].as<
decltype(rhs.
after)>();
528 rhs.
cmd = node[
"cmd"].as<
decltype(rhs.
cmd)>();
538 rhs.
notes = node[
"notes"].as<
decltype(rhs.
notes)>();
544template <>
struct convert<string_or_regex> {
545 static bool decode(
const Node &node, string_or_regex &rhs)
547 using namespace std::string_literals;
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)};
554 rhs = string_or_regex{node.as<std::string>()};
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"))
573 match[
"relationships"].as<std::vector<relationship_t>>();
577 rhs.
pattern = node.as<string_or_regex>();
584template <>
struct convert<namespace_or_regex> {
585 static bool decode(
const Node &node, namespace_or_regex &rhs)
587 using namespace std::string_literals;
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)};
594 rhs = namespace_or_regex{node.as<std::string>()};
604 using namespace std::string_literals;
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));
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")
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);
646 string_or_regex(std::move(rx), std::move(pattern));
649 rhs.
name = name.as<std::string>();
653 rhs.
type = element_filter_t::filtered_type::any;
654 rhs.
name = string_or_regex{node.as<std::string>()};
668 rhs.
anyof = std::make_unique<filter>(node[
"anyof"].as<filter>());
672 rhs.
allof = std::make_unique<filter>(node[
"allof"].as<filter>());
675 if (node[
"namespaces"]) {
676 auto namespace_list =
677 node[
"namespaces"].as<
decltype(rhs.
namespaces)>();
678 for (
const auto &ns : namespace_list)
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});
688 if (node[
"module_access"])
690 node[
"module_access"].as<
decltype(rhs.module_access)>();
692 if (node[
"relationships"])
694 node[
"relationships"].as<
decltype(rhs.relationships)>();
696 if (node[
"elements"])
697 rhs.elements = node[
"elements"].as<
decltype(rhs.elements)>();
699 if (node[
"element_types"])
701 node[
"element_types"].as<
decltype(rhs.element_types)>();
703 if (node[
"method_types"])
705 node[
"method_types"].as<
decltype(rhs.method_types)>();
708 rhs.access = node[
"access"].as<
decltype(rhs.access)>();
710 if (node[
"subclasses"])
711 rhs.subclasses = node[
"subclasses"].as<
decltype(rhs.subclasses)>();
714 rhs.parents = node[
"parents"].as<
decltype(rhs.parents)>();
716 if (node[
"specializations"])
717 rhs.specializations =
718 node[
"specializations"].as<
decltype(rhs.specializations)>();
720 if (node[
"dependants"])
721 rhs.dependants = node[
"dependants"].as<
decltype(rhs.dependants)>();
723 if (node[
"dependencies"])
725 node[
"dependencies"].as<
decltype(rhs.dependencies)>();
728 rhs.context = node[
"context"].as<
decltype(rhs.context)>();
731 rhs.paths = node[
"paths"].as<
decltype(rhs.paths)>();
733 if (node[
"callee_types"])
735 node[
"callee_types"].as<
decltype(rhs.callee_types)>();
748 if (node[
"link"].IsMap())
749 rhs.
link = node[
"link"].as<
decltype(rhs.
link)>();
751 rhs.
link.emplace(
".", node[
"link"].as<std::string>());
754 if (node[
"tooltip"]) {
755 if (node[
"tooltip"].IsMap())
758 rhs.
tooltip.emplace(
".", node[
"tooltip"].as<std::string>());
774 if (node[
"revision"])
780 if (node[
"toplevel"])
918 if (node.Type() == NodeType::Sequence) {
919 rhs.
include = node.as<std::vector<string_or_regex>>();
923 if (node.Type() == NodeType::Map) {
926 node[
"include"].as<std::vector<string_or_regex>>();
929 node[
"exclude"].as<std::vector<string_or_regex>>();
943 assert(node.Type() == NodeType::Map);
946 rhs.
hint = hint_t::up;
947 rhs.
entity = node[
"up"].as<std::string>();
949 else if (node[
"down"]) {
950 rhs.
hint = hint_t::down;
951 rhs.
entity = node[
"down"].as<std::string>();
953 else if (node[
"left"]) {
954 rhs.
hint = hint_t::left;
955 rhs.
entity = node[
"left"].as<std::string>();
957 else if (node[
"right"]) {
958 rhs.
hint = hint_t::right;
959 rhs.
entity = node[
"right"].as<std::string>();
961 else if (node[
"together"]) {
962 rhs.
hint = hint_t::together;
963 rhs.
entity = node[
"together"].as<std::vector<std::string>>();
965 else if (node[
"row"]) {
966 rhs.
hint = hint_t::row;
967 rhs.
entity = node[
"row"].as<std::vector<std::string>>();
969 else if (node[
"column"]) {
970 rhs.
hint = hint_t::column;
971 rhs.
entity = node[
"column"].as<std::vector<std::string>>();
986 assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
988 if (node.Type() == NodeType::Scalar) {
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>();
1001 auto index = stoul(key);
1003 it.second.as<relationship_t>();
1005 catch (std::exception &e) {
1022 assert(node.Type() == NodeType::Map);
1025 node[
"type"].as<std::string>());
1027 rhs.
description = node[
"description"].as<std::string>();
1080 auto diagrams = node[
"diagrams"];
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);
1089 if (diagram_config) {
1090 diagram_config->
name = name;
1091 rhs.
diagrams[name] = diagram_config;
1102template <>
struct convert<inja::json> {
1110 if (YAML::convert<int>::decode(node, i)) {
1114 if (YAML::convert<double>::decode(node, d)) {
1118 if (YAML::convert<bool>::decode(node, b)) {
1122 if (YAML::convert<std::string>::decode(node, s)) {
1130 static bool decode(
const Node &node, inja::json &rhs)
1132 switch (node.Type()) {
1133 case YAML::NodeType::Null:
1135 case YAML::NodeType::Scalar:
1136 parse_scalar(node, rhs);
1138 case YAML::NodeType::Sequence:
1139 for (
auto &&array_element : node)
1140 rhs.emplace_back(array_element.as<inja::json>());
1142 case YAML::NodeType::Map:
1143 for (
auto &&it : node)
1144 rhs[it.first.as<std::string>()] = it.second.as<inja::json>();
1160 YAML::Node predefined_templates = YAML::Load(predefined_templates_str);
1167 predefined_templates.as<std::map<std::string, diagram_template>>());
1180void resolve_option_path(YAML::Node &doc,
const std::string &option_name)
1182 std::filesystem::path relative_to_path{
1183 doc[
"relative_to"].as<std::string>()};
1185 relative_to_path = weakly_canonical(relative_to_path);
1187 std::filesystem::path option_path{doc[option_name].as<std::string>()};
1189 if (option_path.is_absolute())
1192 option_path = relative_to_path / option_path;
1193 option_path = option_path.lexically_normal();
1194 option_path.make_preferred();
1196 doc[option_name] = option_path.string();
1201 std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
1206 auto schema_validator = miroir::Validator<YAML::Node>(schema);
1209 std::filesystem::path config_file_path{};
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};
1216 doc = YAML::Load(stdin_stream_str);
1219 doc = YAML::LoadFile(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());
1232 canonical(absolute(std::filesystem::path{config_file}));
1234 "__parent_path", config_file_path.parent_path().string());
1237 LOG_DBG(
"Effective config file path is {}", config_file_path.string());
1245 if (!doc[
"relative_to"]) {
1246 bool paths_relative_to_config_file =
true;
1248 if (doc[
"paths_relative_to_config_file"] &&
1249 !doc[
"paths_relative_to_config_file"].as<bool>())
1250 paths_relative_to_config_file =
false;
1252 if (paths_relative_to_pwd && *paths_relative_to_pwd)
1253 paths_relative_to_config_file =
false;
1255 if (paths_relative_to_config_file)
1256 doc[
"relative_to"] = config_file_path.parent_path().string();
1258 doc[
"relative_to"] = std::filesystem::current_path().string();
1261 if (no_metadata.has_value()) {
1262 doc[
"generate_metadata"] = !no_metadata.value();
1268 if (!doc[
"output_directory"]) {
1269 doc[
"output_directory"] =
".";
1271 resolve_option_path(doc,
"output_directory");
1273 if (!doc[
"compilation_database_dir"]) {
1274 doc[
"compilation_database_dir"] =
".";
1276 resolve_option_path(doc,
"compilation_database_dir");
1291 if (has_key(doc,
"diagrams")) {
1292 auto diagrams = doc[
"diagrams"];
1294 assert(diagrams.Type() == YAML::NodeType::Map);
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>();
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>();
1305 YAML::Node included_node =
1306 YAML::LoadFile(include_path.string());
1308 diagrams[name] = included_node;
1314 auto schema_errors = schema_validator.validate(doc);
1316 if (!schema_errors.empty()) {
1318 std::move(schema_errors));
1322 auto d = doc.as<
config>();
1327 d.initialize_diagram_templates();
1331 catch (YAML::BadFile &e) {
1332 throw std::runtime_error(fmt::format(
1333 "Could not open config file {}: {}", config_file, e.what()));
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));