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());
253 option.set(included_node.as<
254 std::map<std::string, clanguml::config::diagram_template>>());
257 option.set(node[option.name]
258 .as<std::map<std::string,
264 const auto diagram_type = d[
"type"].as<std::string>();
266 if (diagram_type ==
"class") {
267 return std::make_shared<class_diagram>(d.as<
class_diagram>());
269 if (diagram_type ==
"sequence") {
272 if (diagram_type ==
"package") {
275 if (diagram_type ==
"include") {
279 LOG_ERROR(
"Diagrams of type {} are not supported... ", diagram_type);
287template <>
struct convert<
std::filesystem::path> {
288 static bool decode(
const Node &node, std::filesystem::path &rhs)
290 if (!node.IsScalar())
293 rhs = std::filesystem::path{node.as<std::string>()};
302template <>
struct convert<access_t> {
303 static bool decode(
const Node &node, access_t &rhs)
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;
321template <>
struct convert<module_access_t> {
322 static bool decode(
const Node &node, module_access_t &rhs)
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;
338template <>
struct convert<context_direction_t> {
339 static bool decode(
const Node &node, context_direction_t &rhs)
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;
357template <>
struct convert<method_type> {
358 static bool decode(
const Node &node, method_type &rhs)
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_;
385template <>
struct convert<callee_type> {
386 static bool decode(
const Node &node, callee_type &rhs)
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;
421template <>
struct convert<relationship_t> {
422 static bool decode(
const Node &node, relationship_t &rhs)
424 assert(node.Type() == NodeType::Scalar);
426 auto relationship_name = node.as<std::string>();
427 if (relationship_name ==
"extension" ||
428 relationship_name ==
"inheritance") {
429 rhs = relationship_t::kExtension;
431 else if (relationship_name ==
"composition") {
432 rhs = relationship_t::kComposition;
434 else if (relationship_name ==
"aggregation") {
435 rhs = relationship_t::kAggregation;
437 else if (relationship_name ==
"containment") {
438 rhs = relationship_t::kContainment;
440 else if (relationship_name ==
"ownership") {
441 rhs = relationship_t::kOwnership;
443 else if (relationship_name ==
"association") {
444 rhs = relationship_t::kAssociation;
446 else if (relationship_name ==
"instantiation") {
447 rhs = relationship_t::kInstantiation;
449 else if (relationship_name ==
"friendship") {
450 rhs = relationship_t::kFriendship;
452 else if (relationship_name ==
"dependency") {
453 rhs = relationship_t::kDependency;
455 else if (relationship_name ==
"none") {
456 rhs = relationship_t::kNone;
465template <>
struct convert<
std::vector<source_location>> {
466 static bool decode(
const Node &node, std::vector<source_location> &rhs)
468 for (
auto it = node.begin(); it != node.end(); ++it) {
469 const YAML::Node &n = *it;
473 loc.
location = n[
"marker"].as<std::string>();
474 rhs.emplace_back(std::move(loc));
476 else if (n[
"file"] && n[
"line"]) {
479 loc.
location = n[
"file"].as<std::string>() +
":" +
480 n[
"line"].as<std::string>();
481 rhs.emplace_back(std::move(loc));
483 else if (n[
"function"]) {
487 rhs.emplace_back(std::move(loc));
505 rhs.
after = node[
"after"].as<
decltype(rhs.
after)>();
508 rhs.
cmd = node[
"cmd"].as<
decltype(rhs.
cmd)>();
511 rhs.
style = node[
"style"].as<
decltype(rhs.
style)>();
524 rhs.
after = node[
"after"].as<
decltype(rhs.
after)>();
527 rhs.
cmd = node[
"cmd"].as<
decltype(rhs.
cmd)>();
537 rhs.
notes = node[
"notes"].as<
decltype(rhs.
notes)>();
543template <>
struct convert<string_or_regex> {
544 static bool decode(
const Node &node, string_or_regex &rhs)
546 using namespace std::string_literals;
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)};
553 rhs = string_or_regex{node.as<std::string>()};
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"))
572 match[
"relationships"].as<std::vector<relationship_t>>();
576 rhs.
pattern = node.as<string_or_regex>();
583template <>
struct convert<namespace_or_regex> {
584 static bool decode(
const Node &node, namespace_or_regex &rhs)
586 using namespace std::string_literals;
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)};
593 rhs = namespace_or_regex{node.as<std::string>()};
603 using namespace std::string_literals;
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));
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")
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);
645 string_or_regex(std::move(rx), std::move(pattern));
648 rhs.
name = name.as<std::string>();
652 rhs.
type = element_filter_t::filtered_type::any;
653 rhs.
name = string_or_regex{node.as<std::string>()};
667 rhs.
anyof = std::make_unique<filter>(node[
"anyof"].as<filter>());
671 rhs.
allof = std::make_unique<filter>(node[
"allof"].as<filter>());
674 if (node[
"namespaces"]) {
675 auto namespace_list =
676 node[
"namespaces"].as<
decltype(rhs.
namespaces)>();
677 for (
const auto &ns : namespace_list)
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});
687 if (node[
"module_access"])
689 node[
"module_access"].as<
decltype(rhs.module_access)>();
691 if (node[
"relationships"])
693 node[
"relationships"].as<
decltype(rhs.relationships)>();
695 if (node[
"elements"])
696 rhs.elements = node[
"elements"].as<
decltype(rhs.elements)>();
698 if (node[
"element_types"])
700 node[
"element_types"].as<
decltype(rhs.element_types)>();
702 if (node[
"method_types"])
704 node[
"method_types"].as<
decltype(rhs.method_types)>();
707 rhs.access = node[
"access"].as<
decltype(rhs.access)>();
709 if (node[
"subclasses"])
710 rhs.subclasses = node[
"subclasses"].as<
decltype(rhs.subclasses)>();
713 rhs.parents = node[
"parents"].as<
decltype(rhs.parents)>();
715 if (node[
"specializations"])
716 rhs.specializations =
717 node[
"specializations"].as<
decltype(rhs.specializations)>();
719 if (node[
"dependants"])
720 rhs.dependants = node[
"dependants"].as<
decltype(rhs.dependants)>();
722 if (node[
"dependencies"])
724 node[
"dependencies"].as<
decltype(rhs.dependencies)>();
727 rhs.context = node[
"context"].as<
decltype(rhs.context)>();
730 rhs.paths = node[
"paths"].as<
decltype(rhs.paths)>();
732 if (node[
"callee_types"])
734 node[
"callee_types"].as<
decltype(rhs.callee_types)>();
747 if (node[
"link"].IsMap())
748 rhs.
link = node[
"link"].as<
decltype(rhs.
link)>();
750 rhs.
link.emplace(
".", node[
"link"].as<std::string>());
753 if (node[
"tooltip"]) {
754 if (node[
"tooltip"].IsMap())
757 rhs.
tooltip.emplace(
".", node[
"tooltip"].as<std::string>());
773 if (node[
"revision"])
779 if (node[
"toplevel"])
920 if (node.Type() == NodeType::Sequence) {
921 rhs.
include = node.as<std::vector<string_or_regex>>();
925 if (node.Type() == NodeType::Map) {
928 node[
"include"].as<std::vector<string_or_regex>>();
931 node[
"exclude"].as<std::vector<string_or_regex>>();
945 assert(node.Type() == NodeType::Map);
948 rhs.
hint = hint_t::up;
949 rhs.
entity = node[
"up"].as<std::string>();
951 else if (node[
"down"]) {
952 rhs.
hint = hint_t::down;
953 rhs.
entity = node[
"down"].as<std::string>();
955 else if (node[
"left"]) {
956 rhs.
hint = hint_t::left;
957 rhs.
entity = node[
"left"].as<std::string>();
959 else if (node[
"right"]) {
960 rhs.
hint = hint_t::right;
961 rhs.
entity = node[
"right"].as<std::string>();
963 else if (node[
"together"]) {
964 rhs.
hint = hint_t::together;
965 rhs.
entity = node[
"together"].as<std::vector<std::string>>();
967 else if (node[
"row"]) {
968 rhs.
hint = hint_t::row;
969 rhs.
entity = node[
"row"].as<std::vector<std::string>>();
971 else if (node[
"column"]) {
972 rhs.
hint = hint_t::column;
973 rhs.
entity = node[
"column"].as<std::vector<std::string>>();
988 assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
990 if (node.Type() == NodeType::Scalar) {
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>();
1003 auto index = stoul(key);
1005 it.second.as<relationship_t>();
1007 catch (std::exception &e) {
1024 assert(node.Type() == NodeType::Map);
1027 node[
"type"].as<std::string>());
1029 rhs.
description = node[
"description"].as<std::string>();
1085 auto diagrams = node[
"diagrams"];
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);
1094 if (diagram_config) {
1095 diagram_config->
name = name;
1096 rhs.
diagrams[name] = diagram_config;
1107template <>
struct convert<inja::json> {
1115 if (YAML::convert<int>::decode(node, i)) {
1119 if (YAML::convert<double>::decode(node, d)) {
1123 if (YAML::convert<bool>::decode(node, b)) {
1127 if (YAML::convert<std::string>::decode(node, s)) {
1135 static bool decode(
const Node &node, inja::json &rhs)
1137 switch (node.Type()) {
1138 case YAML::NodeType::Null:
1140 case YAML::NodeType::Scalar:
1141 parse_scalar(node, rhs);
1143 case YAML::NodeType::Sequence:
1144 for (
auto &&array_element : node)
1145 rhs.emplace_back(array_element.as<inja::json>());
1147 case YAML::NodeType::Map:
1148 for (
auto &&it : node)
1149 rhs[it.first.as<std::string>()] = it.second.as<inja::json>();
1165 YAML::Node predefined_templates = YAML::Load(predefined_templates_str);
1172 predefined_templates.as<std::map<std::string, diagram_template>>());
1185void resolve_option_path(YAML::Node &doc,
const std::string &option_name)
1187 std::filesystem::path relative_to_path{
1188 doc[
"relative_to"].as<std::string>()};
1190 relative_to_path = weakly_canonical(relative_to_path);
1192 std::filesystem::path option_path{doc[option_name].as<std::string>()};
1194 if (option_path.is_absolute())
1197 option_path = relative_to_path / option_path;
1198 option_path = option_path.lexically_normal();
1199 option_path.make_preferred();
1201 doc[option_name] = option_path.string();
1206 std::optional<bool> paths_relative_to_pwd, std::optional<bool> no_metadata,
1211 auto schema_validator = miroir::Validator<YAML::Node>(schema);
1214 std::filesystem::path config_file_path{};
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};
1221 doc = YAML::Load(stdin_stream_str);
1224 doc = YAML::LoadFile(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());
1237 canonical(absolute(std::filesystem::path{config_file}));
1239 "__parent_path", config_file_path.parent_path().string());
1242 LOG_DBG(
"Effective config file path is {}", config_file_path.string());
1250 if (!doc[
"relative_to"]) {
1251 bool paths_relative_to_config_file =
true;
1253 if (doc[
"paths_relative_to_config_file"] &&
1254 !doc[
"paths_relative_to_config_file"].as<bool>())
1255 paths_relative_to_config_file =
false;
1257 if (paths_relative_to_pwd && *paths_relative_to_pwd)
1258 paths_relative_to_config_file =
false;
1260 if (paths_relative_to_config_file)
1261 doc[
"relative_to"] = config_file_path.parent_path().string();
1263 doc[
"relative_to"] = std::filesystem::current_path().string();
1266 if (no_metadata.has_value()) {
1267 doc[
"generate_metadata"] = !no_metadata.value();
1273 if (!doc[
"output_directory"]) {
1274 doc[
"output_directory"] =
".";
1276 resolve_option_path(doc,
"output_directory");
1278 if (!doc[
"compilation_database_dir"]) {
1279 doc[
"compilation_database_dir"] =
".";
1281 resolve_option_path(doc,
"compilation_database_dir");
1296 if (has_key(doc,
"diagrams")) {
1297 auto diagrams = doc[
"diagrams"];
1299 assert(diagrams.Type() == YAML::NodeType::Map);
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>();
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>();
1310 YAML::Node included_node =
1311 YAML::LoadFile(include_path.string());
1313 diagrams[name] = included_node;
1319 auto schema_errors = schema_validator.validate(doc);
1321 if (!schema_errors.empty()) {
1323 std::move(schema_errors));
1327 auto d = doc.as<
config>();
1332 d.initialize_diagram_templates();
1336 catch (YAML::BadFile &e) {
1337 throw std::runtime_error(fmt::format(
1338 "Could not open config file {}: {}", config_file, e.what()));
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));