Added initial configuration file schema validation

This commit is contained in:
Bartek Kryza
2023-07-22 18:38:45 +02:00
parent 97efbbb332
commit 8dc0dacd3f
11 changed files with 1550 additions and 73 deletions

View File

@@ -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

View File

@@ -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());

View File

@@ -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<std::vector<std::string>> glob{"glob"};
option<common::model::namespace_> using_namespace{"using_namespace"};
option<bool> include_relations_also_as_members{
@@ -424,7 +430,7 @@ struct inheritable_diagram_options {
"generate_method_arguments", method_arguments::full};
option<bool> group_methods{"group_methods", true};
option<member_order_t> member_order{
"method_order", member_order_t::lexical};
"member_order", member_order_t::lexical};
option<bool> generate_packages{"generate_packages", false};
option<package_type_t> package_type{
"package_type", package_type_t::kNamespace};
@@ -622,8 +628,6 @@ config load(const std::string &config_file,
std::optional<bool> paths_relative_to_pwd = {},
std::optional<bool> no_metadata = {});
config load_plain(const std::string &config_file);
} // namespace config
namespace config {

216
src/config/schema.h Normal file
View File

@@ -0,0 +1,216 @@
/**
* @file src/config/schema.h
*
* Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <string>
namespace clanguml::config {
const std::string schema_str = R"(
types:
map_t<K;V>: { $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<string;string>
layout_hint_key: !variant [up, left, right, down, row, column, together]
layout_hint_value: [string, [string]]
layout_hint_t: [map_t<layout_hint_key;layout_hint_value>]
layout_t: map_t<string;layout_hint_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<string;any>
generate_links: !optional generate_links_t
git: !optional git_t
glob: !optional [string]
include: !optional map_t<string;any>
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<string;string>
relationship_hints: !optional map_t<string;any>
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<string;any>
generate_links: !optional generate_links_t
git: !optional git_t
glob: !optional [string]
include: !optional map_t<string;any>
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<string;any>
generate_links: !optional generate_links_t
git: !optional git_t
glob: !optional [string]
include: !optional map_t<string;any>
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<string;any>
generate_links: !optional generate_links_t
git: !optional git_t
glob: !optional [string]
include: !optional map_t<string;any>
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<string;diagram_template_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<string;diagram_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<string;any>
generate_links: !optional generate_links_t
git: !optional git_t
glob: !optional [string]
include: !optional map_t<string;any>
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

View File

@@ -18,6 +18,11 @@
#include "config.h"
#include "diagram_templates.h"
#include "schema.h"
#define MIROIR_IMPLEMENTATION
#define MIROIR_YAMLCPP_SPECIALIZATION
#include <miroir/miroir.hpp>
namespace YAML {
using clanguml::common::namespace_or_regex;
@@ -766,28 +771,13 @@ template <> struct convert<config> {
auto diagrams = node["diagrams"];
assert(diagrams.Type() == NodeType::Map);
for (auto d : diagrams) {
auto name = d.first.as<std::string>();
std::shared_ptr<clanguml::config::diagram> diagram_config{};
auto parent_path = node["__parent_path"].as<std::string>();
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<std::string>();
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<bool> paths_relative_to_pwd, std::optional<bool> no_metadata)
{
try {
auto schema = YAML::Load(clanguml::config::schema_str);
auto schema_validator = miroir::Validator<YAML::Node>(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::string>();
std::shared_ptr<clanguml::config::diagram> diagram_config{};
auto parent_path = doc["__parent_path"].as<std::string>();
if (has_key(d.second, "include!")) {
auto include_path = std::filesystem::path{parent_path};
include_path /= d.second["include!"].as<std::string>();
YAML::Node 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<config>();
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<config>();
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

View File

@@ -26,8 +26,10 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const string_or_regex &m)
out << std::get<std::string>(m.value());
}
else {
out << YAML::BeginMap;
out << YAML::Key << "r" << YAML::Value
<< std::get<regex>(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<common::model::namespace_>(m.value());
}
else {
out << YAML::BeginMap;
out << YAML::Key << "r" << YAML::Value
<< std::get<regex>(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<const class_diagram *>(&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<const sequence_diagram *>(&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<const package_diagram *>(&c) != nullptr) {
out << c.generate_packages;
out << c.package_type;
}
else if (dynamic_cast<const include_diagram *>(&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;
}

View File

@@ -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

View File

@@ -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"
@@ -340,3 +341,44 @@ TEST_CASE("Test config sequence inherited", "[unit-test]")
CHECK(def.combine_free_functions_into_file_participants() == false);
CHECK(def.generate_return_types() == false);
}
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<const char *> 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();
}

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -28,7 +28,6 @@ diagrams:
- src/**/*.h
using_namespace:
- clanguml
generate_method_arguments: full
layout:
ABCD:
- up: ABCD_SUBCLASS