702 lines
21 KiB
C++
702 lines
21 KiB
C++
/**
|
|
* src/config/config.cc
|
|
*
|
|
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <filesystem>
|
|
|
|
namespace clanguml {
|
|
namespace config {
|
|
|
|
config load(const std::string &config_file)
|
|
{
|
|
try {
|
|
YAML::Node doc = YAML::LoadFile(config_file);
|
|
|
|
// Store the parent path of the config_file to properly resolve
|
|
// the include files paths
|
|
auto config_file_path =
|
|
std::filesystem::absolute(std::filesystem::path{config_file});
|
|
doc.force_insert(
|
|
"__parent_path", config_file_path.parent_path().string());
|
|
|
|
// If the current directory is also a git repository,
|
|
// load some config values, which can be included in the
|
|
// generated diagrams
|
|
if (util::is_git_repository() && !doc["git"]) {
|
|
YAML::Node git_config{YAML::NodeType::Map};
|
|
git_config["branch"] = util::get_git_branch();
|
|
git_config["revision"] = util::get_git_revision();
|
|
git_config["commit"] = util::get_git_commit();
|
|
git_config["toplevel"] = util::get_git_toplevel_dir();
|
|
|
|
doc["git"] = git_config;
|
|
}
|
|
|
|
return doc.as<config>();
|
|
}
|
|
catch (YAML::BadFile &e) {
|
|
throw std::runtime_error(fmt::format(
|
|
"Could not open config file {}: {}", config_file, e.what()));
|
|
}
|
|
catch (YAML::Exception &e) {
|
|
throw std::runtime_error(fmt::format(
|
|
"Cannot parse YAML file {}: {}", config_file, e.what()));
|
|
}
|
|
}
|
|
|
|
std::string to_string(const hint_t t)
|
|
{
|
|
switch (t) {
|
|
case hint_t::up:
|
|
return "up";
|
|
case hint_t::down:
|
|
return "down";
|
|
case hint_t::left:
|
|
return "left";
|
|
case hint_t::right:
|
|
return "right";
|
|
default:
|
|
assert(false);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void plantuml::append(const plantuml &r)
|
|
{
|
|
before.insert(before.end(), r.before.begin(), r.before.end());
|
|
after.insert(after.end(), r.after.begin(), r.after.end());
|
|
}
|
|
|
|
void inheritable_diagram_options::inherit(
|
|
const inheritable_diagram_options &parent)
|
|
{
|
|
glob.override(parent.glob);
|
|
using_namespace.override(parent.using_namespace);
|
|
include_relations_also_as_members.override(
|
|
parent.include_relations_also_as_members);
|
|
include.override(parent.include);
|
|
exclude.override(parent.exclude);
|
|
puml.override(parent.puml);
|
|
generate_method_arguments.override(parent.generate_method_arguments);
|
|
generate_links.override(parent.generate_links);
|
|
generate_system_headers.override(parent.generate_system_headers);
|
|
git.override(parent.git);
|
|
base_directory.override(parent.base_directory);
|
|
relative_to.override(parent.relative_to);
|
|
}
|
|
|
|
common::model::diagram_t class_diagram::type() const
|
|
{
|
|
return common::model::diagram_t::kClass;
|
|
}
|
|
|
|
common::model::diagram_t sequence_diagram::type() const
|
|
{
|
|
return common::model::diagram_t::kSequence;
|
|
}
|
|
|
|
common::model::diagram_t package_diagram::type() const
|
|
{
|
|
return common::model::diagram_t::kPackage;
|
|
}
|
|
|
|
common::model::diagram_t include_diagram::type() const
|
|
{
|
|
return common::model::diagram_t::kInclude;
|
|
}
|
|
|
|
void class_diagram::initialize_relationship_hints()
|
|
{
|
|
using common::model::relationship_t;
|
|
|
|
if (!relationship_hints().count("std::vector")) {
|
|
relationship_hints().insert({"std::vector", {}});
|
|
}
|
|
if (!relationship_hints().count("std::unique_ptr")) {
|
|
relationship_hints().insert({"std::unique_ptr", {}});
|
|
}
|
|
if (!relationship_hints().count("std::shared_ptr")) {
|
|
relationship_hints().insert(
|
|
{"std::shared_ptr", {relationship_t::kAssociation}});
|
|
}
|
|
if (!relationship_hints().count("std::weak_ptr")) {
|
|
relationship_hints().insert(
|
|
{"std::weak_ptr", {relationship_t::kAssociation}});
|
|
}
|
|
if (!relationship_hints().count("std::tuple")) {
|
|
relationship_hints().insert({"std::tuple", {}});
|
|
}
|
|
if (!relationship_hints().count("std::map")) {
|
|
relationship_hint_t hint{relationship_t::kNone};
|
|
hint.argument_hints.insert({1, relationship_t::kAggregation});
|
|
relationship_hints().insert({"std::tuple", std::move(hint)});
|
|
}
|
|
}
|
|
|
|
void class_diagram::initialize_template_aliases()
|
|
{
|
|
if (!template_aliases().count("std::basic_string<char>")) {
|
|
template_aliases().insert({"std::basic_string<char>", "std::string"});
|
|
}
|
|
if (!template_aliases().count("std::basic_string<wchar_t>")) {
|
|
template_aliases().insert(
|
|
{"std::basic_string<wchar_t>", "std::wstring"});
|
|
}
|
|
if (!template_aliases().count("std::basic_string<char16_t>")) {
|
|
template_aliases().insert(
|
|
{"std::basic_string<char16_t>", "std::u16string"});
|
|
}
|
|
if (!template_aliases().count("std::basic_string<char32_t>")) {
|
|
template_aliases().insert(
|
|
{"std::basic_string<char32_t>", "std::u32string"});
|
|
}
|
|
}
|
|
|
|
template <> void append_value<plantuml>(plantuml &l, const plantuml &r)
|
|
{
|
|
l.append(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace YAML {
|
|
using clanguml::common::model::access_t;
|
|
using clanguml::common::model::relationship_t;
|
|
using clanguml::config::class_diagram;
|
|
using clanguml::config::config;
|
|
using clanguml::config::filter;
|
|
using clanguml::config::generate_links_config;
|
|
using clanguml::config::git_config;
|
|
using clanguml::config::hint_t;
|
|
using clanguml::config::include_diagram;
|
|
using clanguml::config::layout_hint;
|
|
using clanguml::config::method_arguments;
|
|
using clanguml::config::package_diagram;
|
|
using clanguml::config::plantuml;
|
|
using clanguml::config::relationship_hint_t;
|
|
using clanguml::config::sequence_diagram;
|
|
using clanguml::config::source_location;
|
|
|
|
inline bool has_key(const YAML::Node &n, const std::string &key)
|
|
{
|
|
assert(n.Type() == NodeType::Map);
|
|
|
|
return std::count_if(n.begin(), n.end(), [&key](auto &&n) {
|
|
return n.first.template as<std::string>() == key;
|
|
}) > 0;
|
|
}
|
|
|
|
template <typename T>
|
|
void get_option(const Node &node, clanguml::config::option<T> &option)
|
|
{
|
|
if (node[option.name])
|
|
option.set(node[option.name].template as<T>());
|
|
}
|
|
|
|
template <>
|
|
void get_option<clanguml::common::model::namespace_>(const Node &node,
|
|
clanguml::config::option<clanguml::common::model::namespace_> &option)
|
|
{
|
|
if (node[option.name]) {
|
|
if (node[option.name].Type() == NodeType::Scalar)
|
|
option.set({node[option.name].template as<std::string>()});
|
|
else if (node[option.name].Type() == NodeType::Sequence)
|
|
option.set(
|
|
{node[option.name].template as<std::vector<std::string>>()[0]});
|
|
else
|
|
throw std::runtime_error("Invalid using_namespace value");
|
|
}
|
|
}
|
|
|
|
template <>
|
|
void get_option<method_arguments>(
|
|
const Node &node, clanguml::config::option<method_arguments> &option)
|
|
{
|
|
if (node[option.name]) {
|
|
const auto &val = node[option.name].as<std::string>();
|
|
if (val == "full")
|
|
option.set(method_arguments::full);
|
|
else if (val == "abbreviated")
|
|
option.set(method_arguments::abbreviated);
|
|
else if (val == "none")
|
|
option.set(method_arguments::none);
|
|
else
|
|
throw std::runtime_error(
|
|
"Invalid generate_method_arguments value: " + val);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<clanguml::config::diagram> parse_diagram_config(const Node &d)
|
|
{
|
|
const auto diagram_type = d["type"].as<std::string>();
|
|
|
|
if (diagram_type == "class") {
|
|
return std::make_shared<class_diagram>(d.as<class_diagram>());
|
|
}
|
|
else if (diagram_type == "sequence") {
|
|
return std::make_shared<sequence_diagram>(d.as<sequence_diagram>());
|
|
}
|
|
else if (diagram_type == "package") {
|
|
return std::make_shared<package_diagram>(d.as<package_diagram>());
|
|
}
|
|
else if (diagram_type == "include") {
|
|
return std::make_shared<include_diagram>(d.as<include_diagram>());
|
|
}
|
|
|
|
LOG_ERROR("Diagrams of type {} are not supported... ", diagram_type);
|
|
|
|
return {};
|
|
}
|
|
|
|
//
|
|
// config std::filesystem::path decoder
|
|
//
|
|
template <> struct convert<std::filesystem::path> {
|
|
static bool decode(const Node &node, std::filesystem::path &rhs)
|
|
{
|
|
if (!node.IsScalar())
|
|
return false;
|
|
|
|
rhs = std::filesystem::path{node.as<std::string>()};
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// config access_t decoder
|
|
//
|
|
template <> struct convert<access_t> {
|
|
static bool decode(const Node &node, access_t &rhs)
|
|
{
|
|
if (node.as<std::string>() == "public")
|
|
rhs = access_t::kPublic;
|
|
else if (node.as<std::string>() == "protected")
|
|
rhs = access_t::kProtected;
|
|
else if (node.as<std::string>() == "private")
|
|
rhs = access_t::kPrivate;
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// config relationship_t decoder
|
|
//
|
|
template <> struct convert<relationship_t> {
|
|
static bool decode(const Node &node, relationship_t &rhs)
|
|
{
|
|
assert(node.Type() == NodeType::Scalar);
|
|
|
|
auto relationship_name = node.as<std::string>();
|
|
if (relationship_name == "extension" ||
|
|
relationship_name == "inheritance") {
|
|
rhs = relationship_t::kExtension;
|
|
}
|
|
else if (relationship_name == "composition") {
|
|
rhs = relationship_t::kComposition;
|
|
}
|
|
else if (relationship_name == "aggregation") {
|
|
rhs = relationship_t::kAggregation;
|
|
}
|
|
else if (relationship_name == "containment") {
|
|
rhs = relationship_t::kContainment;
|
|
}
|
|
else if (relationship_name == "ownership") {
|
|
rhs = relationship_t::kOwnership;
|
|
}
|
|
else if (relationship_name == "association") {
|
|
rhs = relationship_t::kAssociation;
|
|
}
|
|
else if (relationship_name == "instantiation") {
|
|
rhs = relationship_t::kInstantiation;
|
|
}
|
|
else if (relationship_name == "friendship") {
|
|
rhs = relationship_t::kFriendship;
|
|
}
|
|
else if (relationship_name == "dependency") {
|
|
rhs = relationship_t::kDependency;
|
|
}
|
|
else if (relationship_name == "none") {
|
|
rhs = relationship_t::kNone;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <> struct convert<std::vector<source_location>> {
|
|
static bool decode(const Node &node, std::vector<source_location> &rhs)
|
|
{
|
|
for (auto it = node.begin(); it != node.end(); ++it) {
|
|
const YAML::Node &n = *it;
|
|
if (n["usr"]) {
|
|
source_location loc;
|
|
loc.location_type = source_location::location_t::usr;
|
|
loc.location = n["usr"].as<std::string>();
|
|
rhs.emplace_back(std::move(loc));
|
|
}
|
|
else if (n["marker"]) {
|
|
source_location loc;
|
|
loc.location_type = source_location::location_t::marker;
|
|
loc.location = n["marker"].as<std::string>();
|
|
rhs.emplace_back(std::move(loc));
|
|
}
|
|
else if (n["file"] && n["line"]) {
|
|
source_location loc;
|
|
loc.location_type = source_location::location_t::fileline;
|
|
loc.location = n["file"].as<std::string>() + ":" +
|
|
n["line"].as<std::string>();
|
|
rhs.emplace_back(std::move(loc));
|
|
}
|
|
else if (n["function"]) {
|
|
source_location loc;
|
|
loc.location_type = source_location::location_t::function;
|
|
loc.location = n["function"].as<std::string>();
|
|
rhs.emplace_back(std::move(loc));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <> struct convert<plantuml> {
|
|
static bool decode(const Node &node, plantuml &rhs)
|
|
{
|
|
if (node["before"])
|
|
rhs.before = node["before"].as<decltype(rhs.before)>();
|
|
|
|
if (node["after"])
|
|
rhs.after = node["after"].as<decltype(rhs.after)>();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// filter Yaml decoder
|
|
//
|
|
template <> struct convert<filter> {
|
|
static bool decode(const Node &node, filter &rhs)
|
|
{
|
|
if (node["namespaces"]) {
|
|
auto namespace_list =
|
|
node["namespaces"].as<std::vector<std::string>>();
|
|
for (const auto &ns : namespace_list)
|
|
rhs.namespaces.push_back({ns});
|
|
}
|
|
|
|
if (node["relationships"])
|
|
rhs.relationships =
|
|
node["relationships"].as<decltype(rhs.relationships)>();
|
|
|
|
if (node["elements"])
|
|
rhs.elements = node["elements"].as<decltype(rhs.elements)>();
|
|
|
|
if (node["access"])
|
|
rhs.access = node["access"].as<decltype(rhs.access)>();
|
|
|
|
if (node["subclasses"])
|
|
rhs.subclasses = node["subclasses"].as<decltype(rhs.subclasses)>();
|
|
|
|
if (node["specializations"])
|
|
rhs.specializations =
|
|
node["specializations"].as<decltype(rhs.specializations)>();
|
|
|
|
if (node["dependants"])
|
|
rhs.dependants = node["dependants"].as<decltype(rhs.dependants)>();
|
|
|
|
if (node["dependencies"])
|
|
rhs.dependencies =
|
|
node["dependencies"].as<decltype(rhs.dependencies)>();
|
|
|
|
if (node["context"])
|
|
rhs.context = node["context"].as<decltype(rhs.context)>();
|
|
|
|
if (node["paths"])
|
|
rhs.paths = node["paths"].as<decltype(rhs.paths)>();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// generate_links_config Yaml decoder
|
|
//
|
|
template <> struct convert<generate_links_config> {
|
|
static bool decode(const Node &node, generate_links_config &rhs)
|
|
{
|
|
if (node["link"])
|
|
rhs.link = node["link"].as<decltype(rhs.link)>();
|
|
|
|
if (node["tooltip"])
|
|
rhs.tooltip = node["tooltip"].as<decltype(rhs.tooltip)>();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// git_config Yaml decoder
|
|
//
|
|
template <> struct convert<git_config> {
|
|
static bool decode(const Node &node, git_config &rhs)
|
|
{
|
|
if (node["branch"])
|
|
rhs.branch = node["branch"].as<decltype(rhs.branch)>();
|
|
|
|
if (node["revision"])
|
|
rhs.revision = node["revision"].as<decltype(rhs.revision)>();
|
|
|
|
if (node["commit"])
|
|
rhs.commit = node["commit"].as<decltype(rhs.commit)>();
|
|
|
|
if (node["toplevel"])
|
|
rhs.toplevel = node["toplevel"].as<decltype(rhs.toplevel)>();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename T> bool decode_diagram(const Node &node, T &rhs)
|
|
{
|
|
get_option(node, rhs.glob);
|
|
get_option(node, rhs.using_namespace);
|
|
get_option(node, rhs.include);
|
|
get_option(node, rhs.exclude);
|
|
get_option(node, rhs.puml);
|
|
get_option(node, rhs.git);
|
|
get_option(node, rhs.generate_links);
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// class_diagram Yaml decoder
|
|
//
|
|
template <> struct convert<class_diagram> {
|
|
static bool decode(const Node &node, class_diagram &rhs)
|
|
{
|
|
if (!decode_diagram(node, rhs))
|
|
return false;
|
|
|
|
get_option(node, rhs.classes);
|
|
get_option(node, rhs.layout);
|
|
get_option(node, rhs.include_relations_also_as_members);
|
|
get_option(node, rhs.generate_method_arguments);
|
|
get_option(node, rhs.generate_packages);
|
|
get_option(node, rhs.relationship_hints);
|
|
get_option(node, rhs.template_aliases);
|
|
|
|
rhs.initialize_relationship_hints();
|
|
rhs.initialize_template_aliases();
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// sequence_diagram Yaml decoder
|
|
//
|
|
template <> struct convert<sequence_diagram> {
|
|
static bool decode(const Node &node, sequence_diagram &rhs)
|
|
{
|
|
if (!decode_diagram(node, rhs))
|
|
return false;
|
|
|
|
get_option(node, rhs.start_from);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// package_diagram Yaml decoder
|
|
//
|
|
template <> struct convert<package_diagram> {
|
|
static bool decode(const Node &node, package_diagram &rhs)
|
|
{
|
|
if (!decode_diagram(node, rhs))
|
|
return false;
|
|
|
|
get_option(node, rhs.layout);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// include_diagram Yaml decoder
|
|
//
|
|
template <> struct convert<include_diagram> {
|
|
static bool decode(const Node &node, include_diagram &rhs)
|
|
{
|
|
if (!decode_diagram(node, rhs))
|
|
return false;
|
|
|
|
get_option(node, rhs.layout);
|
|
get_option(node, rhs.relative_to);
|
|
get_option(node, rhs.generate_system_headers);
|
|
|
|
// Convert the path in relative_to to an absolute path, with respect
|
|
// to the directory where the `.clang-uml` configuration file is located
|
|
if (rhs.relative_to) {
|
|
auto absolute_relative_to =
|
|
std::filesystem::path{node["__parent_path"].as<std::string>()} /
|
|
rhs.relative_to();
|
|
rhs.relative_to.set(absolute_relative_to.lexically_normal());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// layout_hint Yaml decoder
|
|
//
|
|
template <> struct convert<layout_hint> {
|
|
static bool decode(const Node &node, layout_hint &rhs)
|
|
{
|
|
assert(node.Type() == NodeType::Map);
|
|
|
|
if (node["up"]) {
|
|
rhs.hint = hint_t::up;
|
|
rhs.entity = node["up"].as<std::string>();
|
|
}
|
|
else if (node["down"]) {
|
|
rhs.hint = hint_t::down;
|
|
rhs.entity = node["down"].as<std::string>();
|
|
}
|
|
else if (node["left"]) {
|
|
rhs.hint = hint_t::left;
|
|
rhs.entity = node["left"].as<std::string>();
|
|
}
|
|
else if (node["right"]) {
|
|
rhs.hint = hint_t::right;
|
|
rhs.entity = node["right"].as<std::string>();
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// relationship_hint_t Yaml decoder
|
|
//
|
|
template <> struct convert<relationship_hint_t> {
|
|
static bool decode(const Node &node, relationship_hint_t &rhs)
|
|
{
|
|
assert(node.Type() == NodeType::Map || node.Type() == NodeType::Scalar);
|
|
|
|
if (node.Type() == NodeType::Scalar) {
|
|
// This will be default relationship hint for all arguments
|
|
// of this template (useful for instance for tuples)
|
|
rhs.default_hint = node.as<relationship_t>();
|
|
}
|
|
else {
|
|
for (const auto &it : node) {
|
|
auto key = it.first.as<std::string>();
|
|
if (key == "default") {
|
|
rhs.default_hint = node["default"].as<relationship_t>();
|
|
}
|
|
else {
|
|
try {
|
|
auto index = stoul(key);
|
|
rhs.argument_hints[index] =
|
|
it.second.as<relationship_t>();
|
|
}
|
|
catch (std::exception &e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//
|
|
// config Yaml decoder
|
|
//
|
|
template <> struct convert<config> {
|
|
|
|
static bool decode(const Node &node, config &rhs)
|
|
{
|
|
get_option(node, rhs.glob);
|
|
get_option(node, rhs.using_namespace);
|
|
get_option(node, rhs.output_directory);
|
|
get_option(node, rhs.compilation_database_dir);
|
|
get_option(node, rhs.include_relations_also_as_members);
|
|
get_option(node, rhs.puml);
|
|
get_option(node, rhs.generate_method_arguments);
|
|
get_option(node, rhs.generate_packages);
|
|
get_option(node, rhs.generate_links);
|
|
get_option(node, rhs.generate_system_headers);
|
|
get_option(node, rhs.git);
|
|
rhs.base_directory.set(node["__parent_path"].as<std::string>());
|
|
get_option(node, rhs.relative_to);
|
|
|
|
auto diagrams = node["diagrams"];
|
|
|
|
assert(diagrams.Type() == NodeType::Map);
|
|
|
|
for (auto d : diagrams) {
|
|
auto name = d.first.as<std::string>();
|
|
std::shared_ptr<clanguml::config::diagram> diagram_config{};
|
|
auto parent_path = node["__parent_path"].as<std::string>();
|
|
|
|
if (has_key(d.second, "include!")) {
|
|
auto include_path = std::filesystem::path{parent_path};
|
|
include_path /= d.second["include!"].as<std::string>();
|
|
|
|
YAML::Node node = YAML::LoadFile(include_path.string());
|
|
node.force_insert("__parent_path", parent_path);
|
|
|
|
diagram_config = parse_diagram_config(node);
|
|
}
|
|
else {
|
|
d.second.force_insert("__parent_path", parent_path);
|
|
diagram_config = parse_diagram_config(d.second);
|
|
}
|
|
|
|
if (diagram_config) {
|
|
diagram_config->name = name;
|
|
diagram_config->inherit(rhs);
|
|
rhs.diagrams[name] = diagram_config;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
}
|