From 97efbbb33290d4366c0b90334c1ced927c5bc160 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 22 Jul 2023 18:36:37 +0200 Subject: [PATCH 1/6] Added miroir yaml validation library --- README.md | 1 + thirdparty/miroir/LICENSE | 21 + thirdparty/miroir/miroir.hpp | 1153 ++++++++++++++++++++++++++++++++++ 3 files changed, 1175 insertions(+) create mode 100644 thirdparty/miroir/LICENSE create mode 100644 thirdparty/miroir/miroir.hpp diff --git a/README.md b/README.md index 92684c78..8e7f281e 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,7 @@ This project relies on the following great tools: * [spdlog](https://github.com/gabime/spdlog) - Fast C++ logging library * [Doxygen](https://www.doxygen.nl/) - C++ documentation generator * [Doxygen Awesome](https://jothepro.github.io/doxygen-awesome-css) - Doxygen CSS style +* [miroir](https://gitlab.com/madyanov/miroir) - YAML schema validation library for C++ ## Contributing diff --git a/thirdparty/miroir/LICENSE b/thirdparty/miroir/LICENSE new file mode 100644 index 00000000..8e5238b2 --- /dev/null +++ b/thirdparty/miroir/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Roman Madyanov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/thirdparty/miroir/miroir.hpp b/thirdparty/miroir/miroir.hpp new file mode 100644 index 00000000..d8b3b678 --- /dev/null +++ b/thirdparty/miroir/miroir.hpp @@ -0,0 +1,1153 @@ +#ifndef MIROIR_MIROIR_HPP +#define MIROIR_MIROIR_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace miroir { + +// wrapper around any custom node type +template struct NodeAccessor { + // node iterator type + // for sequence: contains children nodes directly + // for map: contains key-value pairs + using Iterator = void; + + // returns true if node exists + static auto is_defined(const Node &node) -> bool; + // returns true if node is explicitly typed (e.g. quoted for strings) + static auto is_explicit(const Node &node) -> bool; + + // self-explanatory + static auto is_scalar(const Node &node) -> bool; + static auto is_sequence(const Node &node) -> bool; + static auto is_map(const Node &node) -> bool; + + // returns child node by the key/index + template static auto at(const Node &node, const Key &key) -> Node; + + // casts node to type T + template static auto as(const Node &node) -> T; + // casts node to type T or returns a fallback value of the same type + template static auto as(const Node &node, const T &fallback) -> T; + + // returns tag of the node + static auto tag(const Node &node) -> std::string; + // returns string representation of the node + static auto dump(const Node &node) -> std::string; + + // returns true if both nodes have the same content + static auto equals(const Node &lhs, const Node &rhs) -> bool; + // returns true if both nodes point to the same memory + static auto is_same(const Node &lhs, const Node &rhs) -> bool; + + // returns number of children for map and sequence nodes + static auto size(const Node &node) -> std::size_t; + // returns beginning of the node iterator + static auto begin(const Node &node) -> Iterator; + // returns ending of the node iterator + static auto end(const Node &node) -> Iterator; +}; + +enum class ErrorType { + NodeNotFound, // : node not found + InvalidValueType, // : expected value type: + InvalidValue, // : expected value: + MissingKeyWithType, // : missing key with type: + UndefinedNode, // : undefined node +}; + +template struct Error { + ErrorType type; + std::string path; // path of the node in the document + std::variant expected; // expected type + + // errors that occurred during the validation of type variants + std::vector>> variant_errors; + + // returns user-friendly error message + // int max_depth - maximum depth of nested errors (0 = infinite depth, 1 = flat, etc.) + auto description(int max_depth = 0) const -> std::string; +}; + +template class Validator { + public: + using Error = miroir::Error; + using NodeAccessor = miroir::NodeAccessor; + using TypeValidator = auto(*)(const Node &val) -> bool; + + public: + explicit Validator(const Node &schema, + const std::map &type_validators = {}); + + auto validate(const Node &doc) const -> std::vector; + + private: + struct SchemaSettings { + bool default_required; + std::string optional_tag; + std::string required_tag; + std::string embed_tag; + std::string variant_tag; + std::string key_type_prefix; + std::string generic_brackets; + std::string generic_separator; + std::string attribute_separator; + bool ignore_attributes; + }; + + struct GenericType { + std::string name; + std::vector args; + }; + + struct Context { + std::string path; + // note: prohibit assignment to a Node, since it may be a reference type, and assignment to + // an old node may invalidate/replace internal memory of that node (at least the yaml-cpp + // YAML::Node has this behavior) + const std::variant expected; + std::map where; + bool is_embed; + + explicit Context(const Node &expected) + : path{"/"}, expected{expected}, where{}, is_embed{false} {} + + explicit Context(const Context &other, + const std::variant &expected) + : path{other.path}, expected{expected}, where{other.where}, is_embed{other.is_embed} {} + + auto appending_path(const std::string &suffix) const -> Context; + auto with_expected(const std::variant &expected) const + -> Context; + auto with_where(const std::map &where) const -> Context; + auto with_embed() const -> Context; + }; + + private: + static auto schema_settings(const Node &schema) -> SchemaSettings; + static auto schema_types(const Node &schema) -> std::map; + static auto schema_root(const Node &schema) -> Node; + + private: + auto make_error(ErrorType type, const Context &ctx, + const std::vector> &variant_errors = {}) const -> Error; + + void validate(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const; + + void validate_type(const Node &doc, const std::string &type, const Context &ctx, + std::vector &errors) const; + auto validate_type(const Node &doc, const std::string &type, const Context &ctx) const -> bool; + + void validate_scalar(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const; + void validate_sequence(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const; + void validate_map(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const; + + auto tag_is_optional(const std::string &tag) const -> bool; + auto tag_is_embed(const std::string &tag) const -> bool; + auto tag_is_variant(const std::string &tag) const -> bool; + auto tag_is_required(const std::string &tag) const -> bool; + + auto find_node(const Node &map, const std::string &key) const -> std::optional; + + auto type_is_generic(const std::string &type) const -> bool; + auto parse_generic_type(const std::string &type) const -> GenericType; + auto make_generic_args(const GenericType &keys, const GenericType &vals, + const std::map &where) const + -> std::map; + + private: + const SchemaSettings m_settings; + const std::map m_types; + const Node m_root; + + std::map m_validators; + + mutable std::map m_generic_types_cache; +}; + +} // namespace miroir + +#endif // ifndef MIROIR_MIROIR_HPP + +#ifdef MIROIR_IMPLEMENTATION + +#include +#include +#include +#include +#include + +// MIROIR_ASSERT macro +#ifndef MIROIR_ASSERT +#ifndef NDEBUG +#include +#define MIROIR_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + /* todo: (c++20) use std::format */ \ + std::cerr << "FATAL " << __FILE__ << ":" << __LINE__ << ": assertion failure ( " \ + << #cond << " )" << std::endl; \ + std::cerr << "FATAL " << msg << std::endl; \ + std::abort(); \ + } \ + } while (false) +#else // ifndef NDEBUG +#define MIROIR_ASSERT(cond, msg) +#endif // ifndef NDEBUG +#endif // ifndef MIROIR_ASSER + +namespace miroir { + +namespace impl { + +/// Misc + +[[noreturn]] inline void unreachable() { + // Uses compiler specific extensions if possible. + // Even if no extension is used, undefined behavior is still raised by + // an empty function body and the noreturn attribute. +#ifdef __GNUC__ // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif +} + +/// Strings + +auto string_is_prefixed(const std::string &str, const std::string &prefix) -> bool { + return str.compare(0, prefix.size(), prefix) == 0; +} + +auto string_trim_after(const std::string &str, char c) -> std::string_view { + const std::string::size_type pos = str.find(c); + + if (pos != std::string::npos) { + return std::string_view{str}.substr(0, pos); + } else { + return std::string_view{str}; + } +} + +auto string_indent(const std::string &str) -> std::string { + std::istringstream iss{str}; + + std::string line; + line.reserve(str.size()); + + std::string result; + line.reserve(str.size()); + + while (std::getline(iss, line, '\n')) { + // todo: (c++20) use std::format + result += "\n\t\t" + line; + } + + return result; +} + +/// Nodes + +template +auto nodes_contains_node(const std::vector &nodes, const Node &target) -> bool { + for (const Node &node : nodes) { + if (NodeAccessor::is_same(node, target)) { + return true; + } + } + + return false; +} + +/// Errors + +template +auto dump_expected(const std::variant &expected) -> std::string { + using NodeAccessor = NodeAccessor; + + if (std::holds_alternative(expected)) { + return ""; + } else if (std::holds_alternative(expected)) { + return std::get(expected); + } + + const Node node = std::get(expected); + + if (!NodeAccessor::is_sequence(node) || NodeAccessor::size(node) <= 1) { + return NodeAccessor::dump(node); + } + + std::string str = "one of"; + + for (auto it = NodeAccessor::begin(node); it != NodeAccessor::end(node); ++it) { + // todo: (c++20) use std::format + str += "\n\t- " + NodeAccessor::dump(*it); + } + + return str; +} + +template +auto dump_variant_errors(const std::vector>> &variant_errors, int max_depth) + -> std::string { + + if (max_depth == 0) { + return ""; + } + + std::string result; + + for (std::size_t i = 0; i < variant_errors.size(); ++i) { + // todo: (c++20) use std::format + result += "\n\t* failed variant " + std::to_string(i) + ":"; + + for (const Error &err : variant_errors[i]) { + result += impl::string_indent(err.description(max_depth)); + } + } + + return result; +} + +template +void filter_undefined_node_errors(std::vector> &errors, std::size_t embed_count) { + using Error = Error; + + // remove errors that are not present in all embedded nodes + errors.erase(std::remove_if(errors.begin(), errors.end(), + [&errors, embed_count](const Error &remove_err) -> bool { + if (remove_err.type != ErrorType::UndefinedNode) { + return false; + } + + const std::size_t count = std::count_if( + errors.cbegin(), errors.cend(), + [&remove_err](const Error &count_err) -> bool { + return count_err.type == remove_err.type && + count_err.path == remove_err.path; + }); + + return count < embed_count + 1; + }), + errors.end()); + + // remove duplicate errors preserving the order and keeping only the last occurrence of error + std::set> visited_errors; + errors.erase(errors.begin(), + std::stable_partition(errors.rbegin(), errors.rend(), + [&visited_errors](const Error &err) -> bool { + if (err.type != ErrorType::UndefinedNode) { + return true; + } + + if (visited_errors.count({err.type, err.path}) > 0) { + return false; + } + + visited_errors.insert({err.type, err.path}); + return true; + }) + .base()); +} + +/// Built-in validators + +template auto node_is_integer(const Node &node) -> bool { + using NodeAccessor = NodeAccessor; + + if (!NodeAccessor::is_scalar(node)) { + return false; + } + + const std::string val = NodeAccessor::template as(node); + std::istringstream iss{val}; + + long long integer; + iss >> integer; + + return iss.eof() && !iss.fail(); +} + +template auto node_is_number(const Node &node) -> bool { + using NodeAccessor = NodeAccessor; + + if (!NodeAccessor::is_scalar(node)) { + return false; + } + + const std::string val = NodeAccessor::template as(node); + std::istringstream iss{val}; + + double number; + iss >> number; + + return iss.eof() && !iss.fail(); +} + +template auto node_is_boolean(const Node &node) -> bool { + using NodeAccessor = NodeAccessor; + + if (!NodeAccessor::is_scalar(node)) { + return false; + } + + static const struct { + std::string trueval, falseval; + } boolvals[] = { + {"y", "n"}, + {"yes", "no"}, + {"true", "false"}, + {"on", "off"}, + }; + + const std::string val = NodeAccessor::template as(node); + + for (const auto &boolval : boolvals) { + if (val == boolval.trueval || val == boolval.falseval) { + return true; + } + } + + return false; +} + +template auto node_is_string(const Node &node) -> bool { + using NodeAccessor = NodeAccessor; + + if (!NodeAccessor::is_scalar(node)) { + return false; + } + + // value is explicitly quoted + if (NodeAccessor::is_explicit(node)) { + return true; + } + + return !impl::node_is_integer(node) && !impl::node_is_number(node) && + !impl::node_is_boolean(node); +} + +} // namespace impl + +/// Error + +template auto Error::description(int max_depth) const -> std::string { + MIROIR_ASSERT(max_depth >= 0, "max_depth is negative"); + + if (max_depth == 0) { + max_depth = std::numeric_limits::max(); + } + + --max_depth; + + // todo: (c++20) use std::format + switch (type) { + case ErrorType::NodeNotFound: + return path + ": node not found"; + case ErrorType::InvalidValueType: + return path + ": expected value type: " + impl::dump_expected(expected) + + impl::dump_variant_errors(variant_errors, max_depth); + case ErrorType::InvalidValue: + return path + ": expected value: " + impl::dump_expected(expected); + case ErrorType::MissingKeyWithType: + return path + ": missing key with type: " + impl::dump_expected(expected); + case ErrorType::UndefinedNode: + return path + ": undefined node"; + } + + MIROIR_ASSERT(false, "invalid error type: " << static_cast(type)); + // todo: (c++23) use std::unreachable + impl::unreachable(); +} + +/// Context + +template +auto Validator::Context::appending_path(const std::string &suffix) const -> Context { + Context ctx = *this; + // todo: (c++20) use std::format + ctx.path = path != "/" ? path + "." + suffix : path + suffix; + ctx.is_embed = false; // reset is_embed field when we're going deeper + return ctx; +} + +template +auto Validator::Context::with_expected( + const std::variant &expected) const -> Context { + + // see a note for the expected field in the Context struct definition + return Context{*this, expected}; +} + +template +auto Validator::Context::with_where(const std::map &where) const + -> Context { + + Context ctx = *this; + ctx.where = where; + return ctx; +} + +template auto Validator::Context::with_embed() const -> Context { + Context ctx = *this; + ctx.is_embed = true; + return ctx; +} + +/// Validator + +template +Validator::Validator(const Node &schema, + const std::map &type_validators) + : m_settings{schema_settings(schema)}, m_types{schema_types(schema)}, + m_root{schema_root(schema)}, m_validators{type_validators} { + + // todo: add built-in generic types (list, map) + static const std::map builtin_validators = { + // basic + {"any", [](const Node &) -> bool { return true; }}, + {"map", NodeAccessor::is_map}, + {"list", NodeAccessor::is_sequence}, + {"scalar", NodeAccessor::is_scalar}, + + // numeric + {"numeric", impl::node_is_number}, + {"num", impl::node_is_number}, + + // integer + {"integer", impl::node_is_integer}, + {"int", impl::node_is_integer}, + + // bool + {"boolean", impl::node_is_boolean}, + {"bool", impl::node_is_boolean}, + + // string + {"string", impl::node_is_string}, + {"str", impl::node_is_string}, + }; + + m_validators.insert(builtin_validators.cbegin(), builtin_validators.cend()); +} + +template +auto Validator::validate(const Node &doc) const -> std::vector { + const Context ctx{m_root}; + std::vector errors; + validate(doc, m_root, ctx, errors); + return errors; +} + +template +auto Validator::schema_settings(const Node &schema) -> SchemaSettings { + SchemaSettings settings{ + .default_required = true, + .optional_tag = "optional", + .required_tag = "required", + .embed_tag = "embed", + .variant_tag = "variant", + .key_type_prefix = "$", + .generic_brackets = "<>", + .generic_separator = ";", + .attribute_separator = ":", + .ignore_attributes = false, + }; + + const Node settings_node = NodeAccessor::at(schema, "settings"); + + if (NodeAccessor::is_defined(settings_node)) { + settings.default_required = NodeAccessor::as( + NodeAccessor::at(settings_node, "default_required"), settings.default_required); + settings.optional_tag = NodeAccessor::as(NodeAccessor::at(settings_node, "optional_tag"), + settings.optional_tag); + settings.required_tag = NodeAccessor::as(NodeAccessor::at(settings_node, "required_tag"), + settings.required_tag); + settings.embed_tag = + NodeAccessor::as(NodeAccessor::at(settings_node, "embed_tag"), settings.embed_tag); + settings.variant_tag = + NodeAccessor::as(NodeAccessor::at(settings_node, "variant_tag"), settings.variant_tag); + settings.key_type_prefix = NodeAccessor::as( + NodeAccessor::at(settings_node, "key_type_prefix"), settings.key_type_prefix); + settings.generic_brackets = NodeAccessor::as( + NodeAccessor::at(settings_node, "generic_brackets"), settings.generic_brackets); + settings.generic_separator = NodeAccessor::as( + NodeAccessor::at(settings_node, "generic_separator"), settings.generic_separator); + settings.attribute_separator = NodeAccessor::as( + NodeAccessor::at(settings_node, "attribute_separator"), settings.attribute_separator); + settings.ignore_attributes = NodeAccessor::as( + NodeAccessor::at(settings_node, "ignore_attributes"), settings.ignore_attributes); + } + + MIROIR_ASSERT(!settings.optional_tag.empty(), "optional tag name is empty"); + MIROIR_ASSERT(!settings.required_tag.empty(), "required tag name is empty"); + MIROIR_ASSERT(!settings.embed_tag.empty(), "embed tag name is empty"); + MIROIR_ASSERT(!settings.variant_tag.empty(), "variant tag name is empty"); + MIROIR_ASSERT(!settings.key_type_prefix.empty(), "key type prefix is empty"); + + MIROIR_ASSERT(settings.generic_brackets.size() == 2, + "invalid generic brackets string length: " << settings.generic_brackets); + MIROIR_ASSERT(settings.generic_separator.size() == 1, + "invalid generic separator string length: " << settings.generic_separator); + + MIROIR_ASSERT(settings.attribute_separator.size() == 1, + "invalid attribute separator string length: " << settings.attribute_separator); + + return settings; +} + +template +auto Validator::schema_types(const Node &schema) -> std::map { + return NodeAccessor::as(NodeAccessor::at(schema, "types"), std::map{}); +} + +template auto Validator::schema_root(const Node &schema) -> Node { + MIROIR_ASSERT(NodeAccessor::is_map(schema), + "schema is not a map: " << NodeAccessor::dump(schema)); + const Node root = NodeAccessor::at(schema, "root"); + MIROIR_ASSERT(NodeAccessor::is_defined(root), "missing root node in the schema"); + return root; +} + +template +auto Validator::make_error(ErrorType type, const Context &ctx, + const std::vector> &variant_errors) const + -> Error { + + return Error{ + .type = type, + .path = ctx.path, + .expected = ctx.expected, + .variant_errors = variant_errors, + }; +} + +template +void Validator::validate(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const { + + if (NodeAccessor::is_scalar(schema)) { + validate_scalar(doc, schema, ctx, errors); + } else if (NodeAccessor::is_sequence(schema)) { + validate_sequence(doc, schema, ctx, errors); + } else if (NodeAccessor::is_map(schema)) { + validate_map(doc, schema, ctx, errors); + } else { + MIROIR_ASSERT(false, "invalid schema node: " << NodeAccessor::dump(schema)); + } +} + +template +void Validator::validate_type(const Node &doc, const std::string &type, const Context &ctx, + std::vector &errors) const { + + // generic args + // note: generic args can only contain names of other types, but not the types themselves, e.g. + // "generic" is a valid type, but "generic<[string]>" is not, define and use an alias + // (e.g. "list") + if (!ctx.where.empty()) { + const auto concrete_type_it = ctx.where.find(type); + + if (concrete_type_it != ctx.where.end()) { + const std::string &concrete_type = concrete_type_it->second; + validate_type(doc, concrete_type, ctx.with_expected(type).with_where({}), errors); + return; + } + } + + // generic types + if (type_is_generic(type)) { + const GenericType generic_type = parse_generic_type(type); + + for (const auto &[schema_type, schema_type_node] : m_types) { + if (!type_is_generic(schema_type)) { + continue; + } + + const GenericType generic_schema_type = parse_generic_type(schema_type); + if (generic_type.name == generic_schema_type.name) { + const std::map generic_args = + make_generic_args(generic_schema_type, generic_type, ctx.where); + validate(doc, schema_type_node, ctx.with_expected(type).with_where(generic_args), + errors); + return; + } + } + } + + // schema types + const auto type_it = m_types.find(type); + if (type_it != m_types.end()) { + const Node schema_type_node = type_it->second; + validate(doc, schema_type_node, ctx.with_expected(type).with_where({}), errors); + return; + } + + // built-in types + const auto validator_it = m_validators.find(type); + if (validator_it != m_validators.end()) { + const TypeValidator type_validator = validator_it->second; + + if (!type_validator(doc)) { + // node has invalid type + const Error err = make_error(ErrorType::InvalidValueType, ctx.with_expected(type)); + errors.push_back(err); + } + + return; + } + + MIROIR_ASSERT(false, "type not found: " << type); +} + +template +auto Validator::validate_type(const Node &doc, const std::string &type, + const Context &ctx) const -> bool { + + std::vector errors; + validate_type(doc, type, ctx.with_expected(type), errors); + return errors.empty(); +} + +template +void Validator::validate_scalar(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const { + + const std::string type = NodeAccessor::template as(schema); + validate_type(doc, type, ctx, errors); +} + +template +void Validator::validate_sequence(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const { + + const std::size_t schema_size = NodeAccessor::size(schema); + + if (schema_size == 0) { + if (!NodeAccessor::is_sequence(doc)) { + // schema node is an empty sequence but document node is not a sequence + const Error err = make_error(ErrorType::InvalidValueType, ctx); + errors.push_back(err); + } + + // allow any sequence on empty sequence in the schema + return; + } + + const std::string schema_tag = NodeAccessor::tag(schema); + + if (tag_is_variant(schema_tag)) { + for (auto it = NodeAccessor::begin(schema); it != NodeAccessor::end(schema); ++it) { + if (NodeAccessor::equals(doc, *it)) { + // found correct node value + return; + } + } + + // document node has invalid value + const Error err = make_error(ErrorType::InvalidValue, ctx); + errors.push_back(err); + } else if (schema_size == 1) { + const Node child_schema_node = NodeAccessor::at(schema, 0); + + if (NodeAccessor::is_sequence(doc)) { + for (std::size_t i = 0; i < NodeAccessor::size(doc); ++i) { + const Node child_doc_node = NodeAccessor::at(doc, i); + validate(child_doc_node, child_schema_node, ctx.appending_path(std::to_string(i)), + errors); + } + } else { + // schema node is a sequence but document node is not a sequence + const Error err = make_error(ErrorType::InvalidValueType, ctx); + errors.push_back(err); + } + } else { // schema_size > 1 + std::vector> grouped_errors; + std::vector variant_errors; + + for (auto it = NodeAccessor::begin(schema); it != NodeAccessor::end(schema); ++it) { + const Node variant_schema = *it; + + variant_errors.clear(); + validate(doc, variant_schema, ctx.with_expected(variant_schema), variant_errors); + + if (variant_errors.empty()) { + // found correct node type + return; + } + + grouped_errors.push_back(variant_errors); + } + + // document node has invalid type + const Error err = make_error(ErrorType::InvalidValueType, ctx, grouped_errors); + errors.push_back(err); + } +} + +template +void Validator::validate_map(const Node &doc, const Node &schema, const Context &ctx, + std::vector &errors) const { + + const bool doc_is_map = NodeAccessor::is_map(doc); + + if (NodeAccessor::size(schema) == 0) { + if (!doc_is_map) { + // document node must be a map + const Error err = make_error(ErrorType::InvalidValueType, ctx); + errors.push_back(err); + } + + // allow any map on empty map in the schema + return; + } + + std::vector validated_nodes; + std::vector> key_types; // key type, schema val node + + std::size_t embed_count = 0; + bool has_required_nodes = false; + + // validate document structure + for (auto it = NodeAccessor::begin(schema); it != NodeAccessor::end(schema); ++it) { + const Node schema_val_node = it->second; + const std::string schema_val_tag = NodeAccessor::tag(schema_val_node); + + if (tag_is_embed(schema_val_tag)) { + if (doc_is_map) { + validate(doc, schema_val_node, ctx.with_embed(), errors); + ++embed_count; + } + } else { + const Node schema_key_node = it->first; + const std::string key = NodeAccessor::template as(schema_key_node); + + if (!impl::string_is_prefixed(key, m_settings.key_type_prefix)) { + const bool node_is_required = tag_is_required(schema_val_tag); + const std::optional child_doc_node = find_node(doc, key); + const Context child_ctx = ctx.appending_path(key); + + has_required_nodes = has_required_nodes || node_is_required; + + if (child_doc_node.has_value()) { + validate(child_doc_node.value(), schema_val_node, child_ctx, errors); + validated_nodes.push_back(child_doc_node.value()); + } else if (node_is_required) { + // required node not found + const Error err = make_error(ErrorType::NodeNotFound, child_ctx); + errors.push_back(err); + } + } else { + const std::string key_type = key.substr(m_settings.key_type_prefix.size()); + key_types.emplace_back(key_type, schema_val_node); + } + } + } + + if (!doc_is_map) { + if (!has_required_nodes || !key_types.empty()) { + // document node must be a map + const Error err = make_error(ErrorType::InvalidValueType, ctx); + errors.push_back(err); + } + + return; + } + + // validate key types + for (const auto &[key_type, schema_val_node] : key_types) { + const std::string schema_val_tag = NodeAccessor::tag(schema_val_node); + bool key_type_is_valid = !tag_is_required(schema_val_tag); + + for (auto it = NodeAccessor::begin(doc); it != NodeAccessor::end(doc); ++it) { + const Node child_doc_val_node = it->second; + if (impl::nodes_contains_node(validated_nodes, child_doc_val_node)) { + continue; + } + + const Node child_doc_key_node = it->first; + if (!validate_type(child_doc_key_node, key_type, ctx)) { + continue; + } + + key_type_is_valid = true; + + const std::string child_key = + NodeAccessor::template as(child_doc_key_node); + validate(child_doc_val_node, schema_val_node, ctx.appending_path(child_key), errors); + validated_nodes.push_back(child_doc_val_node); + } + + if (!key_type_is_valid) { + // didn't find a key with required type + const Error err = + make_error(ErrorType::MissingKeyWithType, ctx.with_expected(key_type)); + errors.push_back(err); + } + } + + // find undefined nodes + for (auto it = NodeAccessor::begin(doc); it != NodeAccessor::end(doc); ++it) { + const Node child_doc_val_node = it->second; + if (impl::nodes_contains_node(validated_nodes, child_doc_val_node)) { + continue; + } + + const Node child_doc_key_node = it->first; + const std::string child_key = NodeAccessor::template as(child_doc_key_node); + + // node not defined in the schema + const Error err = make_error(ErrorType::UndefinedNode, ctx.appending_path(child_key)); + errors.push_back(err); + } + + // filter UndefinedNode errors + if (!ctx.is_embed) { + impl::filter_undefined_node_errors(errors, embed_count); + } +} + +template +auto Validator::tag_is_optional(const std::string &tag) const -> bool { + return tag == m_settings.optional_tag; +} + +template auto Validator::tag_is_embed(const std::string &tag) const -> bool { + return tag == m_settings.embed_tag; +} + +template +auto Validator::tag_is_variant(const std::string &tag) const -> bool { + return tag == m_settings.variant_tag; +} + +template +auto Validator::tag_is_required(const std::string &tag) const -> bool { + return (m_settings.default_required && !tag_is_optional(tag)) || + (!m_settings.default_required && tag == m_settings.required_tag); +} + +template +auto Validator::find_node(const Node &map, const std::string &key) const + -> std::optional { + + if (!NodeAccessor::is_map(map)) { + return std::nullopt; + } + + const Node node = NodeAccessor::at(map, key); + + if (NodeAccessor::is_defined(node)) { + return node; + } + + if (m_settings.ignore_attributes) { + for (auto it = NodeAccessor::begin(map); it != NodeAccessor::end(map); ++it) { + const Node key_node = it->first; + const Node val_node = it->second; + const std::string node_key = NodeAccessor::template as(key_node); + + if (impl::string_trim_after(node_key, m_settings.attribute_separator[0]) == key) { + return std::optional{val_node}; + } + } + } + + return std::nullopt; +} + +template +auto Validator::type_is_generic(const std::string &type) const -> bool { + return m_generic_types_cache.count(type) > 0 || + type.find(m_settings.generic_brackets[0]) != std::string::npos; +} + +template +auto Validator::parse_generic_type(const std::string &type) const -> GenericType { + const auto cached_generic_type_it = m_generic_types_cache.find(type); + if (cached_generic_type_it != m_generic_types_cache.end()) { + const GenericType &cached_generic_type = cached_generic_type_it->second; + return cached_generic_type; + } + + GenericType generic_type{}; + + enum { + ST_NAME, + ST_ARGS, + ST_SEP, + ST_END, + } state = ST_NAME; + + int level = 0; + std::string_view arg; + + for (auto it = type.cbegin(); it != type.cend(); ++it) { + MIROIR_ASSERT(state != ST_END, "invalid generic parser intermediate state: " << type); + + const unsigned char c = *it; + + if (std::isspace(c)) { + continue; + } + + if (c == m_settings.generic_brackets[0]) { + ++level; + + if (level == 1) { + state = ST_ARGS; + continue; // skip open bracket + } + } else if (c == m_settings.generic_brackets[1]) { + --level; + + if (level == 0) { + state = ST_END; + } + } else if (level == 1 && c == m_settings.generic_separator[0]) { + state = ST_SEP; + } + + switch (state) { + case ST_NAME: + generic_type.name += c; + break; + case ST_ARGS: + arg = std::string_view{!arg.empty() ? arg.cbegin() : &(*it), arg.size() + 1}; + break; + case ST_SEP: + state = ST_ARGS; + [[fallthrough]]; + case ST_END: + MIROIR_ASSERT(!arg.empty(), "generic arg is empty: " << type); + generic_type.args.push_back(std::string{arg}); + arg = std::string_view{}; + break; + default: + MIROIR_ASSERT(false, "invalid generic parser state: " << state); + } + } + + MIROIR_ASSERT(level == 0, "generic brackets are disbalanced: " << type); + MIROIR_ASSERT(state == ST_END, "invalid generic parser end state: " << type); + MIROIR_ASSERT(!generic_type.name.empty(), "generic name is empty: " << type); + MIROIR_ASSERT(!generic_type.args.empty(), "generic args are empty: " << type); + + m_generic_types_cache[type] = generic_type; + + // todo: return reference to cache? + return generic_type; +} + +template +auto Validator::make_generic_args(const GenericType &keys, const GenericType &vals, + const std::map &where) const + -> std::map { + + MIROIR_ASSERT(!keys.args.empty(), "generic args are empty"); + MIROIR_ASSERT(!vals.args.empty(), "generic args are empty"); + MIROIR_ASSERT(keys.args.size() == vals.args.size(), "generic args count mismatch"); + + std::map generic_args; + + for (std::size_t i = 0; i < keys.args.size(); ++i) { + const std::string key = keys.args[i]; + const std::string val = vals.args[i]; + + const auto it = where.find(val); + if (it == where.end()) { + generic_args[key] = val; + } else { + generic_args[key] = it->second; + } + } + + return generic_args; +} + +} // namespace miroir + +#endif // ifdef MIROIR_IMPLEMENTATION + +#ifdef MIROIR_YAMLCPP_SPECIALIZATION + +#include + +namespace miroir { + +template <> struct NodeAccessor { + using Node = YAML::Node; + using Iterator = YAML::const_iterator; + + static auto is_defined(const Node &node) -> bool { return node.IsDefined(); } + + static auto is_explicit(const Node &node) -> bool { + // see: https://yaml.org/spec/1.2.2/#24-tags + // Explicit typing is denoted with a tag using the exclamation point (“!”) symbol. + return node.Tag() == "!"; + } + + static auto is_scalar(const Node &node) -> bool { return node.IsScalar(); } + static auto is_sequence(const Node &node) -> bool { return node.IsSequence(); } + static auto is_map(const Node &node) -> bool { return node.IsMap(); } + + template static auto at(const Node &node, const Key &key) -> Node { + MIROIR_ASSERT(node.IsSequence() || node.IsMap(), + "node is not a sequence or a map: " << dump(node)); + return node[key]; + } + + template static auto as(const Node &node) -> T { return node.as(); } + + template static auto as(const Node &node, const T &fallback) -> T { + return node.as(fallback); + } + + static auto tag(const Node &node) -> std::string { return node.Tag().substr(1); } + + static auto dump(const Node &node) -> std::string { + YAML::Emitter emitter; + emitter.SetSeqFormat(YAML::Flow); + emitter.SetMapFormat(YAML::Flow); + emitter << node; + MIROIR_ASSERT(emitter.good(), "invalid node to emit"); + return emitter.c_str(); + } + + static auto equals(const Node &lhs, const Node &rhs) -> bool { + return lhs == rhs || dump(lhs) == dump(rhs); + } + + static auto is_same(const Node &lhs, const Node &rhs) -> bool { return lhs == rhs; } + + static auto size(const Node &node) -> std::size_t { + MIROIR_ASSERT(node.IsSequence() || node.IsMap(), + "node is not a sequence or a map: " << dump(node)); + return node.size(); + } + + static auto begin(const Node &node) -> Iterator { + MIROIR_ASSERT(node.IsSequence() || node.IsMap(), + "node is not a sequence or a map: " << dump(node)); + return node.begin(); + } + + static auto end(const Node &node) -> Iterator { + MIROIR_ASSERT(node.IsSequence() || node.IsMap(), + "node is not a sequence or a map: " << dump(node)); + return node.end(); + } +}; + +} // namespace miroir + +#endif // ifdef MIROIR_YAMLCPP_SPECIALIZATION From 8dc0dacd3f28fde725eb18f03e008bcbc1701ebe Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 22 Jul 2023 18:38:45 +0200 Subject: [PATCH 2/6] Added initial configuration file schema validation --- docs/test_cases/t00048.md | 1 - src/config/config.cc | 26 + src/config/config.h | 12 +- src/config/schema.h | 216 ++++ src/config/yaml_decoders.cc | 82 +- src/config/yaml_emitters.cc | 74 +- tests/t00048/.clang-uml | 1 - tests/test_config.cc | 48 +- tests/test_config_data/clang_uml_config.yml | 1152 ++++++++++++++++++ tests/test_config_data/diagram_templates.yml | 10 +- tests/test_config_data/layout.yml | 1 - 11 files changed, 1550 insertions(+), 73 deletions(-) create mode 100644 src/config/schema.h create mode 100644 tests/test_config_data/clang_uml_config.yml diff --git a/docs/test_cases/t00048.md b/docs/test_cases/t00048.md index 1ddcb1ed..43b9c2d2 100644 --- a/docs/test_cases/t00048.md +++ b/docs/test_cases/t00048.md @@ -11,7 +11,6 @@ diagrams: - ../../tests/t00048/a_t00048.cc - ../../tests/t00048/t00048.cc using_namespace: clanguml::t00048 - parse_includes: true include: namespaces: - clanguml::t00048 diff --git a/src/config/config.cc b/src/config/config.cc index e50d5170..242b07e0 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -140,6 +140,32 @@ std::string to_string(location_t cp) } } +std::string to_string(package_type_t pt) +{ + switch (pt) { + case package_type_t::kNamespace: + return "namespace"; + case package_type_t::kDirectory: + return "directory"; + default: + assert(false); + return ""; + } +} + +std::string to_string(member_order_t mo) +{ + switch (mo) { + case member_order_t::lexical: + return "lexical"; + case member_order_t::as_is: + return "as_is"; + default: + assert(false); + return ""; + } +} + void plantuml::append(const plantuml &r) { before.insert(before.end(), r.before.begin(), r.before.end()); diff --git a/src/config/config.h b/src/config/config.h index 735ca1dd..46f470c2 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -50,6 +50,8 @@ enum class method_arguments { none /*! Empty string between '(' and ')' */ }; +std::string to_string(method_arguments ma); + /*! Types of methods, which can be used in diagram filters */ enum class method_type { constructor, @@ -84,6 +86,8 @@ enum class package_type_t { kDirectory /*!< From directories */ }; +std::string to_string(package_type_t mt); + /*! How class methods and members should be ordered in diagrams */ enum class member_order_t { lexical, /*! Lexical order based on entire method or member signature @@ -91,7 +95,7 @@ enum class member_order_t { as_is /*! As written in source code */ }; -std::string to_string(method_arguments ma); +std::string to_string(member_order_t mt); /*! Which comment parser should be used */ enum class comment_parser_t { @@ -413,6 +417,8 @@ struct source_location { * @embed{inheritable_diagram_options_context_class.svg} */ struct inheritable_diagram_options { + virtual ~inheritable_diagram_options() = default; + option> glob{"glob"}; option using_namespace{"using_namespace"}; option include_relations_also_as_members{ @@ -424,7 +430,7 @@ struct inheritable_diagram_options { "generate_method_arguments", method_arguments::full}; option group_methods{"group_methods", true}; option member_order{ - "method_order", member_order_t::lexical}; + "member_order", member_order_t::lexical}; option generate_packages{"generate_packages", false}; option package_type{ "package_type", package_type_t::kNamespace}; @@ -622,8 +628,6 @@ config load(const std::string &config_file, std::optional paths_relative_to_pwd = {}, std::optional no_metadata = {}); -config load_plain(const std::string &config_file); - } // namespace config namespace config { diff --git a/src/config/schema.h b/src/config/schema.h new file mode 100644 index 00000000..8e0d608b --- /dev/null +++ b/src/config/schema.h @@ -0,0 +1,216 @@ +/** + * @file src/config/schema.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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. + */ +#pragma once + +#include + +namespace clanguml::config { + +const std::string schema_str = R"( +types: + map_t: { $K: V } + comment_parser_t: !variant [plain, clang] + diagram_type_t: !variant [class, sequence, include, package] + generate_method_arguments_t: !variant [full, abbreviated, none] + generate_links_t: + link: string + tooltip: string + git_t: map_t + layout_hint_key: !variant [up, left, right, down, row, column, together] + layout_hint_value: [string, [string]] + layout_hint_t: [map_t] + layout_t: map_t + package_type_t: !variant [namespace, directory] + member_order_t: !variant [lexical, as_is] + regex_t: + r: string + regex_or_string_t: [string, regex_t] + namespaces_filter_t: regex_or_string_t + elements_filter_t: regex_or_string_t + function_location_t: + function: string + marker_location_t: + marker: string + source_location_t: + - function_location_t + - marker_location_t + class_diagram_t: + type: !variant [class] + # + # Common options + # + __parent_path: !optional string + base_directory: !optional string + comment_parser: !optional comment_parser_t + debug_mode: !optional bool + exclude: !optional map_t + generate_links: !optional generate_links_t + git: !optional git_t + glob: !optional [string] + include: !optional map_t + plantuml: !optional + before: !optional [string] + after: !optional [string] + relative_to: !optional string + using_namespace: !optional [string, [string]] + generate_metadata: !optional bool + # + # Class diagram specific options + # + generate_method_arguments: !optional generate_method_arguments_t + generate_packages: !optional bool + package_type: !optional package_type_t + method_order: !optional member_order_t + member_order: !optional member_order_t + group_methods: !optional bool + type_aliases: !optional map_t + relationship_hints: !optional map_t + include_relations_also_as_members: !optional bool + layout: !optional layout_t + sequence_diagram_t: + type: !variant [sequence] + # + # Common options + # + __parent_path: !optional string + base_directory: !optional string + comment_parser: !optional comment_parser_t + debug_mode: !optional bool + exclude: !optional map_t + generate_links: !optional generate_links_t + git: !optional git_t + glob: !optional [string] + include: !optional map_t + plantuml: !optional + before: !optional [string] + after: !optional [string] + relative_to: !optional string + using_namespace: !optional [string, [string]] + generate_metadata: !optional bool + # + # Sequence diagram specific options + # + generate_method_arguments: !optional generate_method_arguments_t + combine_free_functions_into_file_participants: !optional bool + generate_return_types: !optional bool + generate_condition_statements: !optional bool + participants_order: !optional [string] + start_from: !optional [source_location_t] + package_diagram_t: + type: !variant [package] + # + # Common options + # + __parent_path: !optional string + base_directory: !optional string + comment_parser: !optional comment_parser_t + debug_mode: !optional bool + exclude: !optional map_t + generate_links: !optional generate_links_t + git: !optional git_t + glob: !optional [string] + include: !optional map_t + plantuml: !optional + before: !optional [string] + after: !optional [string] + relative_to: !optional string + using_namespace: !optional [string, [string]] + generate_metadata: !optional bool + # + # Package diagram specific options + # + generate_packages: !optional bool + package_type: !optional package_type_t + layout: !optional layout_t + include_diagram_t: + type: !variant [include] + # + # Common options + # + __parent_path: !optional string + base_directory: !optional string + comment_parser: !optional comment_parser_t + debug_mode: !optional bool + exclude: !optional map_t + generate_links: !optional generate_links_t + git: !optional git_t + glob: !optional [string] + include: !optional map_t + plantuml: !optional + before: !optional [string] + after: !optional [string] + relative_to: !optional string + using_namespace: !optional [string, [string]] + generate_metadata: !optional bool + # + # Include diagram specific options + # + generate_system_headers: !optional bool + diagram_t: + - class_diagram_t + - sequence_diagram_t + - package_diagram_t + - include_diagram_t + diagram_template_t: + description: !optional string + type: diagram_type_t + template: string + diagram_templates_t: map_t + +root: + # + # Root options + # + compilation_database_dir: !optional string + output_directory: !optional string + add_compile_flags: !optional [string] + remove_compile_flags: !optional [string] + diagram_templates: !optional diagram_templates_t + diagrams: !required map_t + # + # Common options + # + __parent_path: !optional string + base_directory: !optional string + comment_parser: !optional comment_parser_t + debug_mode: !optional bool + exclude: !optional map_t + generate_links: !optional generate_links_t + git: !optional git_t + glob: !optional [string] + include: !optional map_t + plantuml: !optional + before: !optional [string] + after: !optional [string] + relative_to: !optional string + using_namespace: !optional [string, [string]] + generate_metadata: !optional bool + # + # Inheritable custom options + # + include_relations_also_as_members: !optional bool + generate_method_arguments: !optional generate_method_arguments_t + combine_free_functions_into_file_participants: !optional bool + generate_return_types: !optional bool + generate_condition_statements: !optional bool + generate_packages: !optional bool + group_methods: !optional bool + package_type: !optional package_type_t +)"; + +} // namespace clanguml::config \ No newline at end of file diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 4f596244..08541326 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -18,6 +18,11 @@ #include "config.h" #include "diagram_templates.h" +#include "schema.h" + +#define MIROIR_IMPLEMENTATION +#define MIROIR_YAMLCPP_SPECIALIZATION +#include namespace YAML { using clanguml::common::namespace_or_regex; @@ -766,28 +771,13 @@ template <> struct convert { auto diagrams = node["diagrams"]; - assert(diagrams.Type() == NodeType::Map); - for (auto d : diagrams) { auto name = d.first.as(); std::shared_ptr diagram_config{}; auto parent_path = node["__parent_path"].as(); + d.second.force_insert("__parent_path", parent_path); - if (has_key(d.second, "include!")) { - auto include_path = std::filesystem::path{parent_path}; - include_path /= d.second["include!"].as(); - - YAML::Node included_node = - YAML::LoadFile(include_path.string()); - included_node.force_insert("__parent_path", parent_path); - - diagram_config = parse_diagram_config(included_node); - } - else { - d.second.force_insert("__parent_path", parent_path); - diagram_config = parse_diagram_config(d.second); - } - + diagram_config = parse_diagram_config(d.second); if (diagram_config) { diagram_config->name = name; diagram_config->inherit(rhs); @@ -844,6 +834,9 @@ config load(const std::string &config_file, std::optional paths_relative_to_pwd, std::optional no_metadata) { try { + auto schema = YAML::Load(clanguml::config::schema_str); + auto schema_validator = miroir::Validator(schema); + YAML::Node doc; std::filesystem::path config_file_path{}; @@ -860,6 +853,8 @@ config load(const std::string &config_file, // Store the parent path of the config_file to properly resolve // the include files paths + if (has_key(doc, "__parent_path")) + doc.remove("__parent_path"); if (config_file == "-") { config_file_path = std::filesystem::current_path(); doc.force_insert("__parent_path", config_file_path.string()); @@ -914,6 +909,38 @@ config load(const std::string &config_file, doc["git"] = git_config; } + // Resolve diagram includes + auto diagrams = doc["diagrams"]; + + assert(diagrams.Type() == YAML::NodeType::Map); + + for (auto d : diagrams) { + auto name = d.first.as(); + std::shared_ptr diagram_config{}; + auto parent_path = doc["__parent_path"].as(); + + if (has_key(d.second, "include!")) { + auto include_path = std::filesystem::path{parent_path}; + include_path /= d.second["include!"].as(); + + YAML::Node included_node = + YAML::LoadFile(include_path.string()); + + diagrams[name] = included_node; + } + } + + auto schema_errors = schema_validator.validate(doc); + + if (schema_errors.size() > 0) { + // print validation errors + for (const auto &err : schema_errors) { + LOG_ERROR("Schema error: {}", err.description()); + } + + throw YAML::Exception({}, "Invalid configuration schema"); + } + auto d = doc.as(); d.initialize_diagram_templates(); @@ -929,25 +956,4 @@ config load(const std::string &config_file, "Cannot parse YAML file {}: {}", config_file, e.what())); } } - -config load_plain(const std::string &config_file) -{ - try { - YAML::Node doc; - std::filesystem::path config_file_path{}; - - doc = YAML::LoadFile(config_file); - - auto d = doc.as(); - return d; - } - 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())); - } -} } // namespace clanguml::config \ No newline at end of file diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 46355f4b..ec3eba8e 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -26,8 +26,10 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const string_or_regex &m) out << std::get(m.value()); } else { + out << YAML::BeginMap; out << YAML::Key << "r" << YAML::Value << std::get(m.value()).pattern; + out << YAML::EndMap; } return out; @@ -39,8 +41,10 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const namespace_or_regex &m) out << std::get(m.value()); } else { + out << YAML::BeginMap; out << YAML::Key << "r" << YAML::Value << std::get(m.value()).pattern; + out << YAML::EndMap; } return out; @@ -88,6 +92,18 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const callee_type &m) return out; } +YAML::Emitter &operator<<(YAML::Emitter &out, const member_order_t &r) +{ + out << to_string(r); + return out; +} + +YAML::Emitter &operator<<(YAML::Emitter &out, const package_type_t &r) +{ + out << to_string(r); + return out; +} + YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f) { out << YAML::BeginMap; @@ -267,32 +283,50 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const config &c) YAML::Emitter &operator<<( YAML::Emitter &out, const inheritable_diagram_options &c) { - out << c.glob; - out << c.using_namespace; - out << c.include_relations_also_as_members; - out << c.include; + // Common options + out << c.base_directory; + out << c.comment_parser; + out << c.debug_mode; out << c.exclude; - out << c.puml; - out << c.generate_method_arguments; - out << c.generate_packages; out << c.generate_links; out << c.git; - out << c.base_directory; + out << c.glob; + out << c.include; + out << c.puml; out << c.relative_to; - out << c.generate_system_headers; - if (c.relationship_hints) { - out << YAML::Key << "relationship_hints" << YAML::Value - << c.relationship_hints(); + out << c.using_namespace; + out << c.generate_metadata; + + if (dynamic_cast(&c) != nullptr) { + out << c.generate_method_arguments; + out << c.generate_packages; + out << c.include_relations_also_as_members; + if (c.relationship_hints) { + out << YAML::Key << "relationship_hints" << YAML::Value + << c.relationship_hints(); + } + + if (c.type_aliases) { + out << YAML::Key << "type_aliases" << YAML::Value + << c.type_aliases(); + } + out << c.member_order; + out << c.package_type; } - if (c.type_aliases) { - out << YAML::Key << "type_aliases" << YAML::Value << c.type_aliases(); + else if (dynamic_cast(&c) != nullptr) { + out << c.combine_free_functions_into_file_participants; + out << c.generate_condition_statements; + out << c.generate_method_arguments; + out << c.generate_return_types; + out << c.participants_order; + } + else if (dynamic_cast(&c) != nullptr) { + out << c.generate_packages; + out << c.package_type; + } + else if (dynamic_cast(&c) != nullptr) { + out << c.generate_system_headers; } - out << c.comment_parser; - out << c.combine_free_functions_into_file_participants; - out << c.generate_return_types; - out << c.generate_condition_statements; - out << c.participants_order; - out << c.debug_mode; return out; } diff --git a/tests/t00048/.clang-uml b/tests/t00048/.clang-uml index 7236ae46..e3e44fbc 100644 --- a/tests/t00048/.clang-uml +++ b/tests/t00048/.clang-uml @@ -8,7 +8,6 @@ diagrams: - ../../tests/t00048/a_t00048.cc - ../../tests/t00048/t00048.cc using_namespace: clanguml::t00048 - parse_includes: true include: namespaces: - clanguml::t00048 \ No newline at end of file diff --git a/tests/test_config.cc b/tests/test_config.cc index c16970fa..d04cb81c 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -15,10 +15,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define CATCH_CONFIG_MAIN - +#define CATCH_CONFIG_RUNNER +#define CATCH_CONFIG_CONSOLE_WIDTH 512 #include "catch.h" +#include "cli/cli_handler.h" #include "config/config.h" #include "util/util.h" @@ -339,4 +340,45 @@ TEST_CASE("Test config sequence inherited", "[unit-test]") CHECK(def.type() == clanguml::common::model::diagram_t::kSequence); CHECK(def.combine_free_functions_into_file_participants() == false); CHECK(def.generate_return_types() == false); -} \ No newline at end of file +} + +TEST_CASE("Test config full clang uml dump", "[unit-test]") +{ + auto cfg = + clanguml::config::load("./test_config_data/clang_uml_config.yml"); + + CHECK(cfg.diagrams.size() == 32); +} + +/// +/// Main test function +/// +int main(int argc, char *argv[]) +{ + Catch::Session session; + using namespace Catch::clara; + + bool debug_log{false}; + auto cli = session.cli() | + Opt(debug_log, "debug_log")["-u"]["--debug-log"]("Enable debug logs"); + + session.cli(cli); + + int returnCode = session.applyCommandLine(argc, argv); + if (returnCode != 0) + return returnCode; + + clanguml::cli::cli_handler clih; + + std::vector argvv = { + "clang-uml", "--config", "./test_config_data/simple.yml"}; + + if (debug_log) + argvv.push_back("-vvv"); + else + argvv.push_back("-q"); + + clih.handle_options(argvv.size(), argvv.data()); + + return session.run(); +} diff --git a/tests/test_config_data/clang_uml_config.yml b/tests/test_config_data/clang_uml_config.yml new file mode 100644 index 00000000..de01db16 --- /dev/null +++ b/tests/test_config_data/clang_uml_config.yml @@ -0,0 +1,1152 @@ +compilation_database_dir: /home/bartek/devel/clang-uml/debug +output_directory: /home/bartek/devel/clang-uml/docs/diagrams +__parent_path: /home/bartek/devel/clang-uml +comment_parser: clang +debug_mode: false +generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" +git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml +relative_to: /home/bartek/devel/clang-uml +generate_metadata: true +diagrams: + architecture_package: + type: package + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/config/config.cc + - src/common/model/diagram.cc + - src/**/model/diagram.cc + - src/**/visitor/translation_unit_visitor.cc + - src/**/generators/**/*generator.cc + include: + namespaces: + - clanguml + elements: + - clanguml + - clanguml::common + - clanguml::config + - r: clanguml::.+_diagram + - r: clanguml::.+::model + - r: clanguml::.+::visitor + - r: clanguml::.+::generators + plantuml: + before: + - title clang-uml top level packages + relative_to: /home/bartek/devel/clang-uml + generate_metadata: true + generate_packages: false + package_type: namespace + architecture_visitors_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + relationships: + - dependency + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/docs/architecture.cc + include: + namespaces: + - clanguml + elements: + - r: clanguml::.+::translation_unit_visitor.* + - r: clanguml::.+::model::diagram + plantuml: + before: + - title clang-uml top level architecture - AST visitors + relative_to: /home/bartek/devel/clang-uml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + class_diagram_generator_sequence: + type: sequence + start_from: + - function: clanguml::class_diagram::generators::plantuml::generator::generate(std::ostream &) const + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/class_diagram/generators/plantuml/*.cc + include: + namespaces: + - clanguml + plantuml: + before: + - title clang-uml clanguml::class_diagram::generators::plantuml::generator sequence diagram + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::class_diagram::generators::plantuml + generate_metadata: true + combine_free_functions_into_file_participants: false + generate_condition_statements: false + generate_method_arguments: full + generate_return_types: false + class_model_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + relationships: + - dependency + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/class_diagram/model/*.cc + include: + namespaces: + - clanguml::common::model + - clanguml::class_diagram::model + plantuml: + before: + - title clang-uml class diagram model + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: none + generate_packages: true + include_relations_also_as_members: false + method_order: lexical + package_type: namespace + cli_handle_options_sequence: + type: sequence + start_from: + - function: clanguml::cli::cli_handler::handle_options(int,const char **) + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: true + exclude: + elements: + - r: clanguml::config::option.* + paths: + - src/util/util.h + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/cli/cli_handler.cc + - src/config/config.cc + - src/config/yaml_decoders.cc + include: + namespaces: + - clanguml + - YAML + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + combine_free_functions_into_file_participants: true + generate_condition_statements: false + generate_method_arguments: none + generate_return_types: false + comment_visitor_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - private + - protected + - public + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/visitor/comment/*.cc + include: + namespaces: + - clanguml::common::visitor::comment + subclasses: + - clanguml::common::visitor::comment::comment_visitor + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + common_model_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + element_types: + - enum + relationships: + - dependency + subclasses: + - clanguml::common::model::filter_visitor + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + include: + namespaces: + - clanguml::common::model + plantuml: + before: + - title clang-uml common diagram model + after: + - "note top of {{ alias(\"diagram\") }}: Common class for specific diagram types" + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::common::model + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: false + method_order: lexical + package_type: namespace + compilation_database_context_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/compilation_database.cc + include: + namespaces: + - clanguml + - clang::tooling + context: + - clanguml::common::compilation_database + relative_to: /home/bartek/devel/clang-uml + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + config_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + elements: + - r: clanguml::config::option<.*> + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/config/config.cc + include: + namespaces: + - clanguml::config + context: + - clanguml::config::inheritable_diagram_options + - clanguml::config::config + - clanguml::config::diagram + plantuml: + before: + - title clang-uml configuration model + after: + - "note left of {{ alias(\"inheritable_diagram_options\") }}: Options common to all diagram types." + - "note right of {{ alias(\"config\") }}: General options not used by diagrams." + - "note bottom of {{ alias(\"class_diagram\") }}: Options for specific class diagrams" + - "note bottom of {{ alias(\"sequence_diagram\") }}: Options for specific sequence diagrams" + - "note bottom of {{ alias(\"package_diagram\") }}: Options for specific package diagrams" + - "note bottom of {{ alias(\"include_diagram\") }}: Options for specific include diagrams" + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::config + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + config_context_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/config/config.cc + include: + namespaces: + - clanguml::config + context: + - clanguml::config::config + plantuml: + before: + - title clanguml::config::config context diagram + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::config + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + decorated_element_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/class_diagram/model/*.cc + - src/sequence_diagram/model/*.cc + - src/include_diagram/model/*.cc + - src/package_diagram/model/*.cc + include: + namespaces: + - clanguml + relationships: + - extension + subclasses: + - clanguml::common::model::decorated_element + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + decorators_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/decorators/decorators.cc + include: + namespaces: + - clanguml::decorators + plantuml: + before: + - title clang-uml decorators model + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::decorators + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: false + method_order: lexical + package_type: namespace + diagram_config_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/config/config.cc + include: + namespaces: + - clanguml + subclasses: + - clanguml::config::diagram + parents: + - clanguml::config::diagram + plantuml: + before: + - title clanguml::config::diagram class hierarchy + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + diagram_element_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - private + - protected + relationships: + - dependency + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/class_diagram/model/*.cc + - src/sequence_diagram/model/*.cc + - src/include_diagram/model/*.cc + - src/package_diagram/model/*.cc + include: + namespaces: + - clanguml + subclasses: + - clanguml::common::model::decorated_element + - clanguml::common::model::source_location + plantuml: + before: + - title clang-uml diagram element class inheritance model + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + diagram_filter_context_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + elements: + - clanguml::common::model::path + method_types: + - constructor + - destructor + - operator + callee_types: + [] + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/diagram_filter.cc + - src/common/model/diagram.cc + include: + namespaces: + - clanguml + context: + - clanguml::common::model::diagram_filter + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + diagram_generate_generic_sequence: + type: sequence + start_from: + - function: clanguml::common::generators::generate_diagram(const std::string &,const std::string &,std::shared_ptr,const common::compilation_database &,const std::vector &,const cli::runtime_config &,std::function &&) + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + paths: + - src/common/model/source_location.h + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/generators/generators.cc + include: + namespaces: + - clanguml::common::generators + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + combine_free_functions_into_file_participants: true + generate_condition_statements: false + generate_method_arguments: none + generate_return_types: false + diagram_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/diagram.cc + - src/class_diagram/model/diagram.cc + - src/sequence_diagram/model/diagram.cc + - src/include_diagram/model/diagram.cc + - src/package_diagram/model/diagram.cc + include: + namespaces: + - clanguml + relationships: + - extension + subclasses: + - clanguml::common::model::diagram + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: none + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + filter_visitor_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/diagram_filter.cc + include: + namespaces: + - clanguml + subclasses: + - clanguml::common::model::filter_visitor + plantuml: + before: + - left to right direction + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + include_graph: + type: include + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/**/*.cc + include: + paths: + - src + plantuml: + before: + - title clang-uml include graph diagram + relative_to: /home/bartek/devel/clang-uml/ + generate_metadata: true + generate_system_headers: false + inheritable_diagram_options_context_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/config/config.cc + include: + namespaces: + - clanguml::config + context: + - clanguml::config::inheritable_diagram_options + plantuml: + before: + - title clanguml::config::config context diagram + - left to right direction + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::config + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + load_config_sequence: + type: sequence + start_from: + - function: clanguml::config::load(const std::string &,std::optional,std::optional) + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + elements: + - r: clanguml::config::option.* + paths: + - src/util/util.h + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/config/config.cc + - src/config/yaml_decoders.cc + include: + namespaces: + - clanguml + - YAML + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + combine_free_functions_into_file_participants: true + generate_condition_statements: false + generate_method_arguments: none + generate_return_types: false + main_sequence: + type: sequence + start_from: + - function: main(int,const char **) + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + elements: + - clanguml::config::option + paths: + - src/util/util.h + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/main.cc + include: + paths: + - src + plantuml: + before: + - title clang-uml main function sequence diagram + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + combine_free_functions_into_file_participants: true + generate_condition_statements: false + generate_method_arguments: none + generate_return_types: false + nested_trait_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - private + - protected + - public + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + include: + namespaces: + - clanguml + subclasses: + - r: .*nested_trait.* + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + package_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - private + - protected + - public + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/package.cc + include: + namespaces: + - clanguml + parents: + - clanguml::common::model::package + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + package_model_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + relationships: + - dependency + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/package_diagram/model/*.cc + include: + namespaces: + - clanguml::common::model + - clanguml::package_diagram::model + plantuml: + before: + - title clang-uml package diagram model + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml::package_diagram::model + generate_metadata: true + generate_method_arguments: none + generate_packages: false + include_relations_also_as_members: false + method_order: lexical + package_type: namespace + relationship_context_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + elements: + - clanguml::common::model::path + relationships: + - dependency + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/relationship.cc + - src/common/model/diagram_element.cc + include: + namespaces: + - clanguml + context: + - clanguml::common::model::relationship + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + sequence_model_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + method_types: + - constructor + - destructor + - operator + relationships: + - dependency + callee_types: + [] + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/sequence_diagram/model/*.cc + include: + namespaces: + - clanguml::common::model + - clanguml::sequence_diagram::model + context: + - clanguml::sequence_diagram::model::diagram + - clanguml::sequence_diagram::model::message + - clanguml::sequence_diagram::model::activity + subclasses: + - clanguml::sequence_diagram::model::participant + plantuml: + before: + - title clang-uml sequence diagram model + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: none + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + source_file_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - private + - protected + - public + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/source_file.cc + include: + namespaces: + - clanguml + parents: + - clanguml::common::model::source_file + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: false + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + source_location_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/class_diagram/model/*.cc + - src/sequence_diagram/model/*.cc + - src/include_diagram/model/*.cc + - src/package_diagram/model/*.cc + include: + namespaces: + - clanguml + relationships: + - extension + subclasses: + - clanguml::common::model::source_location + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + stylable_element_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/class_diagram/model/*.cc + - src/sequence_diagram/model/*.cc + - src/include_diagram/model/*.cc + - src/package_diagram/model/*.cc + include: + namespaces: + - clanguml + relationships: + - extension + subclasses: + - clanguml::common::model::stylable_element + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace + template_builder_sequence: + type: sequence + start_from: + - function: clanguml::class_diagram::visitor::template_builder::build(const clang::NamedDecl *,const clang::TemplateSpecializationType &,std::optional) + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + paths: + - src/common/model/source_location.h + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/class_diagram/visitor/template_builder.cc + include: + namespaces: + - clanguml + paths: + - src/class_diagram/visitor/template_builder.h + - src/class_diagram/visitor/template_builder.cc + plantuml: + before: + - title clang-uml class_diagram::visitor::template_builder::build sequence diagram + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + combine_free_functions_into_file_participants: true + generate_condition_statements: false + generate_method_arguments: none + generate_return_types: false + template_trait_hierarchy_class: + type: class + __parent_path: /home/bartek/devel/clang-uml + comment_parser: clang + debug_mode: false + exclude: + access: + - public + - protected + - private + relationships: + - dependency + generate_links: + link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}" + tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" + git: + branch: add-configuration-file-validation + revision: 0.3.8-2-g561a2b16 + commit: 561a2b16fd48052fc284ac598c9b782be3341662 + toplevel: /home/bartek/devel/clang-uml + glob: + - src/common/model/*.cc + - src/class_diagram/model/*.cc + - src/sequence_diagram/model/*.cc + - src/include_diagram/model/*.cc + - src/package_diagram/model/*.cc + include: + namespaces: + - clanguml + context: + - clanguml::common::model::template_trait + subclasses: + - clanguml::common::model::template_trait + relative_to: /home/bartek/devel/clang-uml + using_namespace: clanguml + generate_metadata: true + generate_method_arguments: full + generate_packages: true + include_relations_also_as_members: true + method_order: lexical + package_type: namespace diff --git a/tests/test_config_data/diagram_templates.yml b/tests/test_config_data/diagram_templates.yml index e1af0e62..e58a60a6 100644 --- a/tests/test_config_data/diagram_templates.yml +++ b/tests/test_config_data/diagram_templates.yml @@ -6,11 +6,11 @@ diagram_templates: description: Sequence diagram of the main() function type: sequence template: | - main_sequence_diagram: - type: sequence - glob: [ {{ glob }} ] - start_from: - - function: 'main(int,const char**)' + main_sequence_diagram: + type: sequence + glob: [ {{ glob }} ] + start_from: + - function: 'main(int,const char**)' diagrams: diagram1: type: class \ No newline at end of file diff --git a/tests/test_config_data/layout.yml b/tests/test_config_data/layout.yml index 2de084e1..71c395f7 100644 --- a/tests/test_config_data/layout.yml +++ b/tests/test_config_data/layout.yml @@ -28,7 +28,6 @@ diagrams: - src/**/*.h using_namespace: - clanguml - generate_method_arguments: full layout: ABCD: - up: ABCD_SUBCLASS From ec0079d8761f9978898b1dc9cca431bd943e32cd Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Jul 2023 01:22:14 +0200 Subject: [PATCH 3/6] Added include/exclude filter specification to validator schema --- src/config/schema.h | 110 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 21 deletions(-) diff --git a/src/config/schema.h b/src/config/schema.h index 8e0d608b..d1bda894 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -24,14 +24,30 @@ namespace clanguml::config { const std::string schema_str = R"( types: map_t: { $K: V } - comment_parser_t: !variant [plain, clang] - diagram_type_t: !variant [class, sequence, include, package] - generate_method_arguments_t: !variant [full, abbreviated, none] + comment_parser_t: !variant + - plain + - clang + diagram_type_t: !variant + - class + - sequence + - include + - package + generate_method_arguments_t: !variant + - full + - abbreviated + - none generate_links_t: link: string tooltip: string git_t: map_t - layout_hint_key: !variant [up, left, right, down, row, column, together] + layout_hint_key: !variant + - up + - left + - right + - down + - row + - column + - together layout_hint_value: [string, [string]] layout_hint_t: [map_t] layout_t: map_t @@ -40,8 +56,65 @@ types: regex_t: r: string regex_or_string_t: [string, regex_t] - namespaces_filter_t: regex_or_string_t - elements_filter_t: regex_or_string_t + namespaces_filter_t: + namespaces: [regex_or_string_t] + elements_filter_t: + elements: [regex_or_string_t] + element_types_filter_t: !variant + - class + - enum + - concept + relationship_filter_t: !variant + - extension + - inheritance + - composition + - aggregation + - containment + - ownership + - association + - instantiation + - friendship + - alias + - dependency + - constraint + - none + access_filter_t: !variant + - public + - protected + - private + method_type_filter_t: !variant + - constructor + - destructor + - assignment + - operator + - defaulted + - deleted + - static + callee_type_filter_t: !variant + - constructor + - assignment + - operator + - defaulted + - static + - method + - function + - function_template + - lambda + filter_t: + namespaces: !optional [regex_or_string_t] + elements: !optional [regex_or_string_t] + element_types: !optional [element_types_filter_t] + relationships: !optional [relationship_filter_t] + access: !optional [access_filter_t] + subclasses: !optional [regex_or_string_t] + parents: !optional [regex_or_string_t] + specializations: !optional [regex_or_string_t] + dependants: !optional [regex_or_string_t] + dependencies: !optional [regex_or_string_t] + context: !optional [regex_or_string_t] + paths: !optional [string] + method_types: !optional [method_type_filter_t] + callee_types: !optional [callee_type_filter_t] function_location_t: function: string marker_location_t: @@ -55,14 +128,13 @@ types: # Common options # __parent_path: !optional string - base_directory: !optional string comment_parser: !optional comment_parser_t debug_mode: !optional bool - exclude: !optional map_t + exclude: !optional filter_t generate_links: !optional generate_links_t git: !optional git_t glob: !optional [string] - include: !optional map_t + include: !optional filter_t plantuml: !optional before: !optional [string] after: !optional [string] @@ -88,14 +160,13 @@ types: # Common options # __parent_path: !optional string - base_directory: !optional string comment_parser: !optional comment_parser_t debug_mode: !optional bool - exclude: !optional map_t + exclude: !optional filter_t generate_links: !optional generate_links_t git: !optional git_t glob: !optional [string] - include: !optional map_t + include: !optional filter_t plantuml: !optional before: !optional [string] after: !optional [string] @@ -117,14 +188,13 @@ types: # Common options # __parent_path: !optional string - base_directory: !optional string comment_parser: !optional comment_parser_t debug_mode: !optional bool - exclude: !optional map_t + exclude: !optional filter_t generate_links: !optional generate_links_t git: !optional git_t glob: !optional [string] - include: !optional map_t + include: !optional filter_t plantuml: !optional before: !optional [string] after: !optional [string] @@ -143,14 +213,13 @@ types: # Common options # __parent_path: !optional string - base_directory: !optional string comment_parser: !optional comment_parser_t debug_mode: !optional bool - exclude: !optional map_t + exclude: !optional filter_t generate_links: !optional generate_links_t git: !optional git_t glob: !optional [string] - include: !optional map_t + include: !optional filter_t plantuml: !optional before: !optional [string] after: !optional [string] @@ -186,14 +255,13 @@ root: # Common options # __parent_path: !optional string - base_directory: !optional string comment_parser: !optional comment_parser_t debug_mode: !optional bool - exclude: !optional map_t + exclude: !optional filter_t generate_links: !optional generate_links_t git: !optional git_t glob: !optional [string] - include: !optional map_t + include: !optional filter_t plantuml: !optional before: !optional [string] after: !optional [string] From 18c40310475e2fc3382d7f6c42747fc1469ec336 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Jul 2023 12:16:24 +0200 Subject: [PATCH 4/6] Added schema validation command line flags --- docs/troubleshooting.md | 7 +++++++ src/cli/cli_handler.cc | 12 ++++++++++- src/cli/cli_handler.h | 2 ++ src/config/config.h | 9 +++++++-- src/config/schema.h | 4 ++++ src/config/yaml_decoders.cc | 20 +++++++++++-------- uml/class/diagram_element_hierarchy_class.yml | 5 ++--- 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 997e3b2e..cb1f75a2 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -200,6 +200,13 @@ the configuration file to `clang-uml` using `stdin`, e.g.: yq 'explode(.)' .clang-uml | clang-uml --config - ``` +## Schema validation error is thrown, but the configuration file is correct +Current version of `clang-uml` performs automatic configuration file +schema validation, and exits if the configuration file is invalid. + +In case there is a bug in the schema validation, the schema validation +step can be skipped by providing `--no-validate` command line option. + ## Class diagrams ### "fatal error: 'stddef.h' file not found" diff --git a/src/cli/cli_handler.cc b/src/cli/cli_handler.cc index 5812045a..85b20fc3 100644 --- a/src/cli/cli_handler.cc +++ b/src/cli/cli_handler.cc @@ -131,6 +131,10 @@ cli_flow_t cli_handler::parse(int argc, const char **argv) "Skip metadata (e.g. clang-uml version) from diagrams"); app.add_flag("--print-start-from", print_start_from, "Print all possible 'start_from' values for a given diagram"); + app.add_flag("--no-validate", no_validate, + "Do not perform configuration file schema validation"); + app.add_flag("--validate-only", validate_only, + "Perform configuration file schema validation and exit"); try { app.parse(argc, argv); @@ -245,7 +249,13 @@ cli_flow_t cli_handler::load_config() { try { config = clanguml::config::load( - config_path, paths_relative_to_pwd, no_metadata); + config_path, paths_relative_to_pwd, no_metadata, !no_validate); + if (validate_only) { + LOG_INFO("Configuration file {} is valid.", config_path); + + return cli_flow_t::kExit; + } + return cli_flow_t::kContinue; } catch (std::runtime_error &e) { diff --git a/src/cli/cli_handler.h b/src/cli/cli_handler.h index b785b14a..a281b70f 100644 --- a/src/cli/cli_handler.h +++ b/src/cli/cli_handler.h @@ -183,6 +183,8 @@ public: std::optional show_template; std::vector generators{ clanguml::common::generator_type_t::plantuml}; + bool no_validate{false}; + bool validate_only{false}; clanguml::config::config config; diff --git a/src/config/config.h b/src/config/config.h index 46f470c2..2cb6a2ba 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -36,6 +36,10 @@ namespace clanguml { +namespace cli { +struct runtime_config; +} // namespace cli + /** * @brief Configuration file related classes * @@ -472,7 +476,7 @@ struct inheritable_diagram_options { * @embed{diagram_config_hierarchy_class.svg} */ struct diagram : public inheritable_diagram_options { - virtual ~diagram() = default; + ~diagram() override = default; virtual common::model::diagram_t type() const = 0; @@ -622,11 +626,12 @@ struct config : public inheritable_diagram_options { * the configuration file or to the current * directory (`$PWD`) * @param no_metadata Whether the diagram should skip metadata at the end + * @param validate If true, perform schema validation * @return Configuration instance */ config load(const std::string &config_file, std::optional paths_relative_to_pwd = {}, - std::optional no_metadata = {}); + std::optional no_metadata = {}, bool validate = true); } // namespace config diff --git a/src/config/schema.h b/src/config/schema.h index d1bda894..62f1c2b5 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -64,6 +64,10 @@ types: - class - enum - concept + - method + - function + - function_template + - lambda relationship_filter_t: !variant - extension - inheritance diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 08541326..22fbf1b4 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -16,6 +16,7 @@ * limitations under the License. */ +#include "cli/cli_handler.h" #include "config.h" #include "diagram_templates.h" #include "schema.h" @@ -831,7 +832,8 @@ void resolve_option_path(YAML::Node &doc, const std::string &option) } // namespace config load(const std::string &config_file, - std::optional paths_relative_to_pwd, std::optional no_metadata) + std::optional paths_relative_to_pwd, std::optional no_metadata, + bool validate) { try { auto schema = YAML::Load(clanguml::config::schema_str); @@ -930,15 +932,17 @@ config load(const std::string &config_file, } } - auto schema_errors = schema_validator.validate(doc); + if (validate) { + auto schema_errors = schema_validator.validate(doc); - if (schema_errors.size() > 0) { - // print validation errors - for (const auto &err : schema_errors) { - LOG_ERROR("Schema error: {}", err.description()); + if (!schema_errors.empty()) { + // print validation errors + for (const auto &err : schema_errors) { + LOG_ERROR("Schema error: {}", err.description()); + } + + throw YAML::Exception({}, "Invalid configuration schema"); } - - throw YAML::Exception({}, "Invalid configuration schema"); } auto d = doc.as(); diff --git a/uml/class/diagram_element_hierarchy_class.yml b/uml/class/diagram_element_hierarchy_class.yml index 60d1792c..3a12c8b0 100644 --- a/uml/class/diagram_element_hierarchy_class.yml +++ b/uml/class/diagram_element_hierarchy_class.yml @@ -12,9 +12,8 @@ include: subclasses: - clanguml::common::model::decorated_element - clanguml::common::model::source_location - include: - relationships: - - inheritance + relationships: + - inheritance exclude: relationships: - dependency From 366bb1991205b2ca8ea6e2f7d5b3caa903aacbc0 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Jul 2023 17:07:22 +0200 Subject: [PATCH 5/6] Minor configuration schema updates --- src/cli/cli_handler.cc | 2 +- src/config/schema.h | 15 +- tests/test_config_data/clang_uml_config.yml | 184 ++++++++++---------- 3 files changed, 105 insertions(+), 96 deletions(-) diff --git a/src/cli/cli_handler.cc b/src/cli/cli_handler.cc index 85b20fc3..6f090ab5 100644 --- a/src/cli/cli_handler.cc +++ b/src/cli/cli_handler.cc @@ -251,7 +251,7 @@ cli_flow_t cli_handler::load_config() config = clanguml::config::load( config_path, paths_relative_to_pwd, no_metadata, !no_validate); if (validate_only) { - LOG_INFO("Configuration file {} is valid.", config_path); + std::cout << "Configuration file " << config_path << " is valid.\n"; return cli_flow_t::kExit; } diff --git a/src/config/schema.h b/src/config/schema.h index 62f1c2b5..a424e02b 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -39,7 +39,11 @@ types: generate_links_t: link: string tooltip: string - git_t: map_t + git_t: + branch: string + revision: string + commit: string + toplevel: string layout_hint_key: !variant - up - left @@ -51,8 +55,12 @@ types: layout_hint_value: [string, [string]] layout_hint_t: [map_t] layout_t: map_t - package_type_t: !variant [namespace, directory] - member_order_t: !variant [lexical, as_is] + package_type_t: !variant + - namespace + - directory + member_order_t: !variant + - lexical + - as_is regex_t: r: string regex_or_string_t: [string, regex_t] @@ -151,7 +159,6 @@ types: generate_method_arguments: !optional generate_method_arguments_t generate_packages: !optional bool package_type: !optional package_type_t - method_order: !optional member_order_t member_order: !optional member_order_t group_methods: !optional bool type_aliases: !optional map_t diff --git a/tests/test_config_data/clang_uml_config.yml b/tests/test_config_data/clang_uml_config.yml index de01db16..93262567 100644 --- a/tests/test_config_data/clang_uml_config.yml +++ b/tests/test_config_data/clang_uml_config.yml @@ -8,8 +8,8 @@ generate_links: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml relative_to: /home/bartek/devel/clang-uml generate_metadata: true @@ -24,8 +24,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/config/config.cc @@ -68,8 +68,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/docs/architecture.cc @@ -87,7 +87,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace class_diagram_generator_sequence: type: sequence @@ -101,8 +101,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/class_diagram/generators/plantuml/*.cc @@ -132,8 +132,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -151,7 +151,7 @@ diagrams: generate_method_arguments: none generate_packages: true include_relations_also_as_members: false - method_order: lexical + member_order: lexical package_type: namespace cli_handle_options_sequence: type: sequence @@ -170,8 +170,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/cli/cli_handler.cc @@ -203,8 +203,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/visitor/comment/*.cc @@ -219,7 +219,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace common_model_class: type: class @@ -242,8 +242,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -261,7 +261,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: false - method_order: lexical + member_order: lexical package_type: namespace compilation_database_context_class: type: class @@ -273,8 +273,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/compilation_database.cc @@ -289,7 +289,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace config_class: type: class @@ -304,8 +304,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/config/config.cc @@ -332,7 +332,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace config_context_class: type: class @@ -344,8 +344,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/config/config.cc @@ -363,7 +363,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace decorated_element_hierarchy_class: type: class @@ -380,8 +380,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -402,7 +402,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace decorators_class: type: class @@ -414,8 +414,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/decorators/decorators.cc @@ -431,7 +431,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: false - method_order: lexical + member_order: lexical package_type: namespace diagram_config_hierarchy_class: type: class @@ -448,8 +448,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/config/config.cc @@ -469,7 +469,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace diagram_element_hierarchy_class: type: class @@ -488,8 +488,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -500,6 +500,8 @@ diagrams: include: namespaces: - clanguml + relationships: + - extension subclasses: - clanguml::common::model::decorated_element - clanguml::common::model::source_location @@ -512,7 +514,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace diagram_filter_context_class: type: class @@ -533,8 +535,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/diagram_filter.cc @@ -550,7 +552,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace diagram_generate_generic_sequence: type: sequence @@ -567,8 +569,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/generators/generators.cc @@ -597,8 +599,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/diagram.cc @@ -619,7 +621,7 @@ diagrams: generate_method_arguments: none generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace filter_visitor_hierarchy_class: type: class @@ -636,8 +638,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/diagram_filter.cc @@ -655,7 +657,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace include_graph: type: include @@ -667,8 +669,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/**/*.cc @@ -691,8 +693,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/config/config.cc @@ -711,7 +713,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace load_config_sequence: type: sequence @@ -730,8 +732,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/config/config.cc @@ -764,8 +766,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/main.cc @@ -797,8 +799,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -813,7 +815,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace package_hierarchy_class: type: class @@ -830,8 +832,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/package.cc @@ -846,7 +848,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace package_model_class: type: class @@ -861,8 +863,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -880,7 +882,7 @@ diagrams: generate_method_arguments: none generate_packages: false include_relations_also_as_members: false - method_order: lexical + member_order: lexical package_type: namespace relationship_context_class: type: class @@ -897,8 +899,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/relationship.cc @@ -914,7 +916,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace sequence_model_class: type: class @@ -935,8 +937,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -960,7 +962,7 @@ diagrams: generate_method_arguments: none generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace source_file_hierarchy_class: type: class @@ -977,8 +979,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/source_file.cc @@ -993,7 +995,7 @@ diagrams: generate_method_arguments: full generate_packages: false include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace source_location_hierarchy_class: type: class @@ -1010,8 +1012,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -1032,7 +1034,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace stylable_element_hierarchy_class: type: class @@ -1049,8 +1051,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -1071,7 +1073,7 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical + member_order: lexical package_type: namespace template_builder_sequence: type: sequence @@ -1088,8 +1090,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/class_diagram/visitor/template_builder.cc @@ -1126,8 +1128,8 @@ diagrams: tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}" git: branch: add-configuration-file-validation - revision: 0.3.8-2-g561a2b16 - commit: 561a2b16fd48052fc284ac598c9b782be3341662 + revision: 0.3.8-6-g18c40310 + commit: 18c40310475e2fc3382d7f6c42747fc1469ec336 toplevel: /home/bartek/devel/clang-uml glob: - src/common/model/*.cc @@ -1148,5 +1150,5 @@ diagrams: generate_method_arguments: full generate_packages: true include_relations_also_as_members: true - method_order: lexical - package_type: namespace + member_order: lexical + package_type: namespace \ No newline at end of file From bcd32bfd414f73e41c6db26710c436538670b4de Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Jul 2023 18:34:03 +0200 Subject: [PATCH 6/6] Fixed building on MSVC --- thirdparty/miroir/miroir.hpp | 37 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/thirdparty/miroir/miroir.hpp b/thirdparty/miroir/miroir.hpp index d8b3b678..b33c30cc 100644 --- a/thirdparty/miroir/miroir.hpp +++ b/thirdparty/miroir/miroir.hpp @@ -550,18 +550,17 @@ auto Validator::validate(const Node &doc) const -> std::vector { template auto Validator::schema_settings(const Node &schema) -> SchemaSettings { - SchemaSettings settings{ - .default_required = true, - .optional_tag = "optional", - .required_tag = "required", - .embed_tag = "embed", - .variant_tag = "variant", - .key_type_prefix = "$", - .generic_brackets = "<>", - .generic_separator = ";", - .attribute_separator = ":", - .ignore_attributes = false, - }; + SchemaSettings settings; + settings.default_required = true; + settings.optional_tag = "optional"; + settings.required_tag = "required"; + settings.embed_tag = "embed"; + settings.variant_tag = "variant"; + settings.key_type_prefix = "$"; + settings.generic_brackets = "<>"; + settings.generic_separator = ";"; + settings.attribute_separator = ":"; + settings.ignore_attributes = false; const Node settings_node = NodeAccessor::at(schema, "settings"); @@ -623,12 +622,12 @@ auto Validator::make_error(ErrorType type, const Context &ctx, const std::vector> &variant_errors) const -> Error { - return Error{ - .type = type, - .path = ctx.path, - .expected = ctx.expected, - .variant_errors = variant_errors, - }; + Error err; + err.type = type; + err.path = ctx.path; + err.expected = ctx.expected; + err.variant_errors = variant_errors; + return err; } template @@ -1022,7 +1021,7 @@ auto Validator::parse_generic_type(const std::string &type) const -> Gener generic_type.name += c; break; case ST_ARGS: - arg = std::string_view{!arg.empty() ? arg.cbegin() : &(*it), arg.size() + 1}; + arg = std::string_view{!arg.empty() ? arg.data() : &(*it), arg.size() + 1}; break; case ST_SEP: state = ST_ARGS;