From 40851b395e6bc68b172773402cc642e3f26f7ec1 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 25 Jun 2024 17:26:14 +0200 Subject: [PATCH] Added initial version of advanced diagram filter config (#289) --- src/common/model/filters/diagram_filter.cc | 119 ++++++++- src/common/model/filters/diagram_filter.h | 75 +++++- .../model/filters/diagram_filter_factory.cc | 241 +++++++++++++----- .../model/filters/diagram_filter_factory.h | 64 ++++- src/config/config.cc | 14 + src/config/config.h | 11 + src/config/schema.h | 6 + src/config/yaml_decoders.cc | 25 ++ tests/CMakeLists.txt | 1 + tests/test_config_data/filters_advanced.yml | 20 ++ tests/test_filters_advanced.cc | 88 +++++++ 11 files changed, 585 insertions(+), 79 deletions(-) create mode 100644 tests/test_config_data/filters_advanced.yml create mode 100644 tests/test_filters_advanced.cc diff --git a/src/common/model/filters/diagram_filter.cc b/src/common/model/filters/diagram_filter.cc index 4a6c8337..2d209528 100644 --- a/src/common/model/filters/diagram_filter.cc +++ b/src/common/model/filters/diagram_filter.cc @@ -169,22 +169,116 @@ anyof_filter::anyof_filter( tvl::value_t anyof_filter::match( const diagram &d, const common::model::element &e) const { - return tvl::any_of(filters_.begin(), filters_.end(), - [&d, &e](const auto &f) { return f->match(d, e); }); + return match_anyof(d, e); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const common::model::relationship_t &r) const +{ + return match_anyof(d, r); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const common::model::access_t &a) const +{ + return match_anyof(d, a); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const common::model::namespace_ &ns) const +{ + return match_anyof(d, ns); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const common::model::source_file &f) const +{ + return match_anyof(d, f); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const common::model::source_location &f) const +{ + return match_anyof(d, f); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const class_diagram::model::class_method &m) const +{ + return match_anyof(d, m); +} + +tvl::value_t anyof_filter::match( + const diagram &d, const class_diagram::model::class_member &m) const +{ + return match_anyof(d, m); } tvl::value_t anyof_filter::match( const diagram &d, const sequence_diagram::model::participant &p) const { - return tvl::any_of(filters_.begin(), filters_.end(), - [&d, &p](const auto &f) { return f->match(d, p); }); + return match_anyof(d, p); } -tvl::value_t anyof_filter::match( - const diagram &d, const common::model::source_file &e) const +allof_filter::allof_filter( + filter_t type, std::vector> filters) + : filter_visitor{type} + , filters_{std::move(filters)} { - return tvl::any_of(filters_.begin(), filters_.end(), - [&d, &e](const auto &f) { return f->match(d, e); }); +} + +tvl::value_t allof_filter::match( + const diagram &d, const common::model::element &e) const +{ + return match_allof(d, e); +} + +tvl::value_t allof_filter::match( + const diagram &d, const common::model::relationship_t &r) const +{ + return match_allof(d, r); +} + +tvl::value_t allof_filter::match( + const diagram &d, const common::model::access_t &a) const +{ + return match_allof(d, a); +} + +tvl::value_t allof_filter::match( + const diagram &d, const common::model::namespace_ &ns) const +{ + return match_allof(d, ns); +} + +tvl::value_t allof_filter::match( + const diagram &d, const common::model::source_file &f) const +{ + return match_allof(d, f); +} + +tvl::value_t allof_filter::match( + const diagram &d, const common::model::source_location &f) const +{ + return match_allof(d, f); +} + +tvl::value_t allof_filter::match( + const diagram &d, const class_diagram::model::class_method &m) const +{ + return match_allof(d, m); +} + +tvl::value_t allof_filter::match( + const diagram &d, const class_diagram::model::class_member &m) const +{ + return match_allof(d, m); +} + +tvl::value_t allof_filter::match( + const diagram &d, const sequence_diagram::model::participant &p) const +{ + return match_allof(d, p); } namespace_filter::namespace_filter( @@ -978,6 +1072,15 @@ diagram_filter::diagram_filter(const common::model::diagram &d, { } +void diagram_filter::add_filter( + filter_t filter_type, std::unique_ptr fv) +{ + if (filter_type == filter_t::kInclusive) + add_inclusive_filter(std::move(fv)); + else + add_exclusive_filter(std::move(fv)); +} + void diagram_filter::add_inclusive_filter(std::unique_ptr fv) { inclusive_.emplace_back(std::move(fv)); diff --git a/src/common/model/filters/diagram_filter.h b/src/common/model/filters/diagram_filter.h index a599df09..2d881255 100644 --- a/src/common/model/filters/diagram_filter.h +++ b/src/common/model/filters/diagram_filter.h @@ -128,12 +128,81 @@ struct anyof_filter : public filter_visitor { const diagram &d, const common::model::element &e) const override; tvl::value_t match(const diagram &d, - const sequence_diagram::model::participant &p) const override; + const common::model::relationship_t &r) const override; tvl::value_t match( - const diagram &d, const common::model::source_file &e) const override; + const diagram &d, const common::model::access_t &a) const override; + + tvl::value_t match( + const diagram &d, const common::model::namespace_ &ns) const override; + + tvl::value_t match( + const diagram &d, const common::model::source_file &f) const override; + + tvl::value_t match(const diagram &d, + const common::model::source_location &f) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::class_method &m) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::class_member &m) const override; + + tvl::value_t match(const diagram &d, + const sequence_diagram::model::participant &p) const override; private: + template + tvl::value_t match_anyof(const diagram &d, const E &element) const + { + return tvl::any_of(filters_.begin(), filters_.end(), + [&d, &element](const auto &f) { return f->match(d, element); }); + } + + std::vector> filters_; +}; + +struct allof_filter : public filter_visitor { + allof_filter( + filter_t type, std::vector> filters); + + ~allof_filter() override = default; + + tvl::value_t match( + const diagram &d, const common::model::element &e) const override; + + tvl::value_t match(const diagram &d, + const common::model::relationship_t &r) const override; + + tvl::value_t match( + const diagram &d, const common::model::access_t &a) const override; + + tvl::value_t match( + const diagram &d, const common::model::namespace_ &ns) const override; + + tvl::value_t match( + const diagram &d, const common::model::source_file &f) const override; + + tvl::value_t match(const diagram &d, + const common::model::source_location &f) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::class_method &m) const override; + + tvl::value_t match(const diagram &d, + const class_diagram::model::class_member &m) const override; + + tvl::value_t match(const diagram &d, + const sequence_diagram::model::participant &p) const override; + +private: + template + tvl::value_t match_allof(const diagram &d, const E &element) const + { + return tvl::all_of(filters_.begin(), filters_.end(), + [&d, &element](const auto &f) { return f->match(d, element); }); + } + std::vector> filters_; }; @@ -681,6 +750,8 @@ public: diagram_filter(const common::model::diagram &d, const config::diagram &c, private_constructor_tag_t unused); + void add_filter(filter_t filter_type, std::unique_ptr fv); + /** * Add inclusive filter. * diff --git a/src/common/model/filters/diagram_filter_factory.cc b/src/common/model/filters/diagram_filter_factory.cc index 4d4dd373..c500d38e 100644 --- a/src/common/model/filters/diagram_filter_factory.cc +++ b/src/common/model/filters/diagram_filter_factory.cc @@ -44,92 +44,92 @@ using source_file_dependency_filter_t = common::model::source_file, std::string, common::model::source_file>; } -void basic_diagram_filter_initializer::initialize( - const config::diagram &c, diagram_filter &df) +void basic_diagram_filter_initializer::initialize() { // Process inclusive filters - if (c.include) { + if (diagram_config.include) { df.add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.include().namespaces)); + filter_t::kInclusive, diagram_config.include().namespaces)); df.add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.include().modules)); + filter_t::kInclusive, diagram_config.include().modules)); df.add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.include().module_access)); + filter_t::kInclusive, diagram_config.include().module_access)); df.add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.include().relationships)); + filter_t::kInclusive, diagram_config.include().relationships)); df.add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.include().access)); + filter_t::kInclusive, diagram_config.include().access)); df.add_inclusive_filter(std::make_unique( - filter_t::kInclusive, c.root_directory(), c.include().paths)); + filter_t::kInclusive, diagram_config.root_directory(), + diagram_config.include().paths)); df.add_inclusive_filter( std::make_unique(filter_t::kInclusive, std::make_unique( - filter_t::kInclusive, c.include().access), - std::make_unique( - filter_t::kInclusive, c.include().method_types))); + filter_t::kInclusive, diagram_config.include().access), + std::make_unique(filter_t::kInclusive, + diagram_config.include().method_types))); df.add_inclusive_filter( std::make_unique(filter_t::kInclusive, std::make_unique( - filter_t::kInclusive, c.include().access))); + filter_t::kInclusive, diagram_config.include().access))); // Include any of these matches even if one them does not match std::vector> element_filters; element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().elements)); + filter_t::kInclusive, diagram_config.include().elements)); element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().element_types)); + filter_t::kInclusive, diagram_config.include().element_types)); - if (c.type() == diagram_t::kClass) { + if (diagram_config.type() == diagram_t::kClass) { element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().subclasses)); + filter_t::kInclusive, diagram_config.include().subclasses)); element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().parents)); + filter_t::kInclusive, diagram_config.include().parents)); element_filters.emplace_back( std::make_unique(filter_t::kInclusive, relationship_t::kInstantiation, - c.include().specializations)); + diagram_config.include().specializations)); element_filters.emplace_back( std::make_unique( filter_t::kInclusive, relationship_t::kDependency, - c.include().dependants)); + diagram_config.include().dependants)); element_filters.emplace_back( std::make_unique( filter_t::kInclusive, relationship_t::kDependency, - c.include().dependencies, true)); + diagram_config.include().dependencies, true)); } - else if (c.type() == diagram_t::kSequence) { + else if (diagram_config.type() == diagram_t::kSequence) { element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().callee_types)); + filter_t::kInclusive, diagram_config.include().callee_types)); } - else if (c.type() == diagram_t::kPackage) { + else if (diagram_config.type() == diagram_t::kPackage) { element_filters.emplace_back( std::make_unique( filter_t::kInclusive, relationship_t::kDependency, - c.include().dependants)); + diagram_config.include().dependants)); element_filters.emplace_back( std::make_unique( filter_t::kInclusive, relationship_t::kDependency, - c.include().dependencies, true)); + diagram_config.include().dependencies, true)); } - else if (c.type() == diagram_t::kInclude) { + else if (diagram_config.type() == diagram_t::kInclude) { std::vector dependants; std::vector dependencies; - for (auto &&path : c.include().dependants) { + for (auto &&path : diagram_config.include().dependants) { if (auto p = path.get(); p.has_value()) { const std::filesystem::path dep_path{*p}; dependants.emplace_back( @@ -137,7 +137,7 @@ void basic_diagram_filter_initializer::initialize( } } - for (auto &&path : c.include().dependencies) { + for (auto &&path : diagram_config.include().dependencies) { if (auto p = path.get(); p.has_value()) { const std::filesystem::path dep_path{*p}; dependencies.emplace_back( @@ -157,90 +157,91 @@ void basic_diagram_filter_initializer::initialize( } element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().context)); + filter_t::kInclusive, diagram_config.include().context)); df.add_inclusive_filter(std::make_unique( filter_t::kInclusive, std::move(element_filters))); } // Process exclusive filters - if (c.exclude) { + if (diagram_config.exclude) { df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().namespaces)); + filter_t::kExclusive, diagram_config.exclude().namespaces)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().modules)); + filter_t::kExclusive, diagram_config.exclude().modules)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().module_access)); + filter_t::kExclusive, diagram_config.exclude().module_access)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.root_directory(), c.exclude().paths)); + filter_t::kExclusive, diagram_config.root_directory(), + diagram_config.exclude().paths)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().elements)); + filter_t::kExclusive, diagram_config.exclude().elements)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().element_types)); + filter_t::kExclusive, diagram_config.exclude().element_types)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().relationships)); + filter_t::kExclusive, diagram_config.exclude().relationships)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().access)); + filter_t::kExclusive, diagram_config.exclude().access)); df.add_exclusive_filter( std::make_unique(filter_t::kExclusive, std::make_unique( - filter_t::kExclusive, c.exclude().access), - std::make_unique( - filter_t::kExclusive, c.exclude().method_types))); + filter_t::kExclusive, diagram_config.exclude().access), + std::make_unique(filter_t::kExclusive, + diagram_config.exclude().method_types))); df.add_exclusive_filter( std::make_unique(filter_t::kExclusive, std::make_unique( - filter_t::kExclusive, c.exclude().access))); + filter_t::kExclusive, diagram_config.exclude().access))); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().subclasses)); + filter_t::kExclusive, diagram_config.exclude().subclasses)); df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().parents)); + filter_t::kExclusive, diagram_config.exclude().parents)); - df.add_exclusive_filter( - std::make_unique(filter_t::kExclusive, - relationship_t::kInstantiation, c.exclude().specializations)); + df.add_exclusive_filter(std::make_unique( + filter_t::kExclusive, relationship_t::kInstantiation, + diagram_config.exclude().specializations)); - if (c.type() == diagram_t::kClass) { + if (diagram_config.type() == diagram_t::kClass) { df.add_exclusive_filter(std::make_unique( filter_t::kExclusive, relationship_t::kDependency, - c.exclude().dependants)); + diagram_config.exclude().dependants)); df.add_exclusive_filter( std::make_unique( filter_t::kExclusive, relationship_t::kDependency, - c.exclude().dependencies, true)); + diagram_config.exclude().dependencies, true)); } - else if (c.type() == diagram_t::kSequence) { + else if (diagram_config.type() == diagram_t::kSequence) { df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().callee_types)); + filter_t::kExclusive, diagram_config.exclude().callee_types)); } - else if (c.type() == diagram_t::kPackage) { + else if (diagram_config.type() == diagram_t::kPackage) { df.add_exclusive_filter( std::make_unique( filter_t::kExclusive, relationship_t::kDependency, - c.exclude().dependencies, true)); + diagram_config.exclude().dependencies, true)); df.add_exclusive_filter( std::make_unique( filter_t::kExclusive, relationship_t::kDependency, - c.exclude().dependants)); + diagram_config.exclude().dependants)); } - else if (c.type() == diagram_t::kInclude) { + else if (diagram_config.type() == diagram_t::kInclude) { std::vector dependants; std::vector dependencies; - for (auto &&path : c.exclude().dependants) { + for (auto &&path : diagram_config.exclude().dependants) { if (auto p = path.get(); p.has_value()) { std::filesystem::path dep_path{*p}; dependants.emplace_back( @@ -248,7 +249,7 @@ void basic_diagram_filter_initializer::initialize( } } - for (auto &&path : c.exclude().dependencies) { + for (auto &&path : diagram_config.exclude().dependencies) { if (auto p = path.get(); p.has_value()) { std::filesystem::path dep_path{*p}; dependencies.emplace_back( @@ -268,13 +269,129 @@ void basic_diagram_filter_initializer::initialize( } df.add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().context)); + filter_t::kExclusive, diagram_config.exclude().context)); } } -void advanced_diagram_filter_initializer::initialize( - const config::diagram &c, diagram_filter &df) +std::vector> +advanced_diagram_filter_initializer::build( + filter_t filter_type, const config::filter &filter_config) { + std::vector> result; + + // At any level, only allof, anyof, or a set of other non-operator + // filters can be present + if (filter_config.allof) { + std::vector> allof_filters = + build(filter_type, *filter_config.allof); + + auto allof_filter = std::make_unique( + filter_type, std::move(allof_filters)); + + result.emplace_back(std::move(allof_filter)); + } + + if (filter_config.anyof) { + std::vector> anyof_filters = + build(filter_type, *filter_config.anyof); + + auto anyof_filter = std::make_unique( + filter_type, std::move(anyof_filters)); + + result.emplace_back(std::move(anyof_filter)); + } + + add_filter(filter_type, filter_config.namespaces, result); + add_filter(filter_type, filter_config.modules, result); + add_filter( + filter_type, filter_config.module_access, result); + add_filter( + filter_type, filter_config.relationships, result); + add_filter(filter_type, filter_config.access, result); + + if (!filter_config.method_types.empty()) { + result.emplace_back(std::make_unique(filter_type, + std::make_unique( + filter_t::kInclusive, filter_config.access), + std::make_unique( + filter_t::kInclusive, filter_config.method_types))); + } + + if (!filter_config.paths.empty()) { + result.emplace_back(std::make_unique( + filter_type, diagram_config.root_directory(), filter_config.paths)); + } + + if (!filter_config.access.empty()) + result.emplace_back(std::make_unique(filter_type, + std::make_unique( + filter_type, filter_config.access))); + + add_filter(filter_type, filter_config.elements, result); + add_filter( + filter_type, filter_config.element_types, result); + add_filter(filter_type, filter_config.subclasses, result); + add_filter(filter_type, filter_config.parents, result); + + add_edge_filter(filter_type, + filter_config.specializations, relationship_t::kInstantiation, false, + result); + add_edge_filter(filter_type, + filter_config.dependants, relationship_t::kDependency, false, result); + add_edge_filter(filter_type, + filter_config.dependencies, relationship_t::kDependency, true, result); + + add_filter(filter_type, filter_config.callee_types, result); + + add_edge_filter(filter_type, + filter_config.dependants, relationship_t::kDependency, false, result); + add_edge_filter(filter_type, + filter_config.dependencies, relationship_t::kDependency, true, result); + + add_filter(filter_type, filter_config.context, result); + + if (diagram_config.type() == diagram_t::kInclude) { + std::vector dependants; + std::vector dependencies; + + for (auto &&path : filter_config.dependants) { + if (auto p = path.get(); p.has_value()) { + const std::filesystem::path dep_path{*p}; + dependants.emplace_back(dep_path.lexically_normal().string()); + } + } + + for (auto &&path : filter_config.dependencies) { + if (auto p = path.get(); p.has_value()) { + const std::filesystem::path dep_path{*p}; + dependencies.emplace_back(dep_path.lexically_normal().string()); + } + } + + add_edge_filter(filter_type, + dependants, relationship_t::kAssociation, false, result); + add_edge_filter(filter_type, + dependencies, relationship_t::kAssociation, true, result); + } + + return result; +} + +void advanced_diagram_filter_initializer::initialize() +{ + if (diagram_config.include) { + auto inclusive_filter = + build(filter_t::kInclusive, diagram_config.include()); + for (auto &f : inclusive_filter) + df.add_filter(filter_t::kInclusive, std::move(f)); + } + + if (diagram_config.exclude) { + auto exclusive_filter = + build(filter_t::kExclusive, diagram_config.exclude()); + for (auto &f : exclusive_filter) + df.add_filter(filter_t::kExclusive, std::move(f)); + } } } // namespace clanguml::common::model diff --git a/src/common/model/filters/diagram_filter_factory.h b/src/common/model/filters/diagram_filter_factory.h index ac5efd1b..37fab51a 100644 --- a/src/common/model/filters/diagram_filter_factory.h +++ b/src/common/model/filters/diagram_filter_factory.h @@ -22,12 +22,57 @@ namespace clanguml::common::model { -struct basic_diagram_filter_initializer { - void initialize(const config::diagram &c, diagram_filter &df); +class diagram_filter_initializer { +public: + diagram_filter_initializer(const config::diagram &c, diagram_filter &filter) + : diagram_config{c} + , df{filter} + { + } + + virtual void initialize() = 0; + +protected: + const config::diagram &diagram_config; + diagram_filter &df; }; -struct advanced_diagram_filter_initializer { - void initialize(const config::diagram &c, diagram_filter &df); +class basic_diagram_filter_initializer : public diagram_filter_initializer { +public: + using diagram_filter_initializer::diagram_filter_initializer; + + void initialize() override; +}; + +class advanced_diagram_filter_initializer : public diagram_filter_initializer { +public: + using diagram_filter_initializer::diagram_filter_initializer; + + void initialize() override; + +private: + std::vector> build( + filter_t filter_type, const config::filter &filter_config); + + template + void add_filter(const filter_t &filter_type, + const std::vector &filter_config, + std::vector> &result) + { + if (!filter_config.empty()) + result.emplace_back( + std::make_unique(filter_type, filter_config)); + } + + template + void add_edge_filter(const filter_t &filter_type, + const std::vector &filter_config, relationship_t rt, bool direction, + std::vector> &result) + { + if (!filter_config.empty()) + result.emplace_back(std::make_unique( + filter_type, rt, filter_config, direction)); + } }; class diagram_filter_factory { @@ -38,9 +83,14 @@ public: auto filter = std::make_unique( d, c, diagram_filter::private_constructor_tag_t{}); - basic_diagram_filter_initializer init; - - init.initialize(c, *filter); + if (c.filter_mode() == config::filter_mode_t::basic) { + basic_diagram_filter_initializer init{c, *filter}; + init.initialize(); + } + else { + advanced_diagram_filter_initializer init{c, *filter}; + init.initialize(); + } return filter; } diff --git a/src/config/config.cc b/src/config/config.cc index b02b7257..18d20908 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -186,6 +186,19 @@ std::string to_string(context_direction_t cd) } } +std::string to_string(filter_mode_t fm) +{ + switch (fm) { + case filter_mode_t::basic: + return "basic"; + case filter_mode_t::advanced: + return "advanced"; + default: + assert(false); + return ""; + } +} + std::optional plantuml::get_style( const common::model::relationship_t relationship_type) const { @@ -228,6 +241,7 @@ void inheritable_diagram_options::inherit( using_module.override(parent.using_module); include_relations_also_as_members.override( parent.include_relations_also_as_members); + filter_mode.override(parent.filter_mode); include.override(parent.include); exclude.override(parent.exclude); puml.override(parent.puml); diff --git a/src/config/config.h b/src/config/config.h index 230d6271..843d448c 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -116,6 +116,13 @@ struct plantuml_keyword_mapping_t { relationships; }; +enum class filter_mode_t { + basic, /*!< Default filter structure without logical operators */ + advanced /*!< Advanced filter config with logical operators */ +}; + +std::string to_string(filter_mode_t cp); + /** * @brief PlantUML diagram config section * @@ -187,6 +194,9 @@ struct diagram_template { }; struct filter { + std::shared_ptr anyof; + std::shared_ptr allof; + /*! @brief Namespaces filter * * Example: @@ -544,6 +554,7 @@ struct inheritable_diagram_options { option using_module{"using_module"}; option include_relations_also_as_members{ "include_relations_also_as_members", true}; + option filter_mode{"filter_mode", filter_mode_t::basic}; option include{"include"}; option exclude{"exclude"}; option puml{"plantuml", option_inherit_mode::kAppend}; diff --git a/src/config/schema.h b/src/config/schema.h index f1e880f6..6b8c597a 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -156,6 +156,8 @@ types: paths: !optional [string] method_types: !optional [method_type_filter_t] callee_types: !optional [callee_type_filter_t] + anyof: !optional filter_t + allof: !optional filter_t function_location_t: function: string marker_location_t: @@ -163,6 +165,9 @@ types: source_location_t: - function_location_t - marker_location_t + filter_mode_t: !variant + - basic + - advanced class_diagram_t: type: !variant [class] # @@ -171,6 +176,7 @@ types: __parent_path: !optional string comment_parser: !optional comment_parser_t debug_mode: !optional bool + filter_mode: !optional filter_mode_t exclude: !optional filter_t generate_links: !optional generate_links_t git: !optional git_t diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index a21335ba..666854a6 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -158,6 +158,21 @@ void get_option(const Node &node, } } +template <> +void get_option(const Node &node, + clanguml::config::option &option) +{ + if (node[option.name]) { + const auto &val = node[option.name].as(); + if (val == "basic") + option.set(clanguml::config::filter_mode_t::basic); + else if (val == "advanced") + option.set(clanguml::config::filter_mode_t::advanced); + else + throw std::runtime_error("Invalid comment_parser value: " + val); + } +} + template <> void get_option>( const Node &node, @@ -521,6 +536,14 @@ template <> struct convert { template <> struct convert { static bool decode(const Node &node, filter &rhs) { + if (node["anyof"]) { + rhs.anyof = std::make_unique(node["anyof"].as()); + } + + if (node["allof"]) { + rhs.anyof = std::make_unique(node["anyof"].as()); + } + if (node["namespaces"]) { auto namespace_list = node["namespaces"].as(); @@ -631,6 +654,7 @@ template bool decode_diagram(const Node &node, T &rhs) get_option(node, rhs.glob); get_option(node, rhs.using_namespace); get_option(node, rhs.using_module); + get_option(node, rhs.filter_mode); get_option(node, rhs.include); get_option(node, rhs.exclude); get_option(node, rhs.puml); @@ -849,6 +873,7 @@ template <> struct convert { get_option(node, rhs.glob); get_option(node, rhs.using_namespace); get_option(node, rhs.using_module); + get_option(node, rhs.filter_mode); get_option(node, rhs.output_directory); get_option(node, rhs.query_driver); get_option(node, rhs.allow_empty_diagrams); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 88a911e9..845bc995 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -93,6 +93,7 @@ set(TEST_NAMES test_config test_cli_handler test_filters + test_filters_advanced test_thread_pool_executor test_query_driver_output_extractor test_progress_indicator) diff --git a/tests/test_config_data/filters_advanced.yml b/tests/test_config_data/filters_advanced.yml new file mode 100644 index 00000000..cdbc55ee --- /dev/null +++ b/tests/test_config_data/filters_advanced.yml @@ -0,0 +1,20 @@ +compilation_database_dir: debug +output_directory: output +diagrams: + anyof_test: + type: class + relative_to: ../../../src + glob: + - src/**/*.cc + - src/**/*.h + filter_mode: advanced + include: + anyof: + namespaces: + - ns1::ns2 + elements: + - std::thread + exclude: + anyof: + namespaces: + - ns1::ns2::detail \ No newline at end of file diff --git a/tests/test_filters_advanced.cc b/tests/test_filters_advanced.cc new file mode 100644 index 00000000..992576cd --- /dev/null +++ b/tests/test_filters_advanced.cc @@ -0,0 +1,88 @@ +/** + * @file tests/test_filters_advanced.cc + * + * Copyright (c) 2021-2024 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. + */ +#define DOCTEST_CONFIG_IMPLEMENT + +#include "doctest/doctest.h" + +#include "class_diagram/model/class.h" +#include "cli/cli_handler.h" +#include "common/model/filters/diagram_filter_factory.h" +#include "common/model/source_file.h" +#include "config/config.h" +#include "include_diagram/model/diagram.h" +#include "sequence_diagram/model/diagram.h" + +#include + +TEST_CASE("Test advanced diagram filter anyof") +{ + using clanguml::common::model::diagram_filter; + using clanguml::common::model::diagram_filter_factory; + using clanguml::common::model::namespace_; + using clanguml::common::model::source_file; + using clanguml::config::filter_mode_t; + + auto cfg = + clanguml::config::load("./test_config_data/filters_advanced.yml"); + + auto &config = *cfg.diagrams["anyof_test"]; + clanguml::include_diagram::model::diagram diagram; + + auto filter_ptr = diagram_filter_factory::create(diagram, config); + diagram_filter &filter = *filter_ptr; + + CHECK(config.filter_mode() == filter_mode_t::advanced); + CHECK(filter.should_include(namespace_{"ns1::ns2"})); + CHECK_FALSE(filter.should_include(namespace_{"std::string"})); + + clanguml::common::model::element std_thread{{}}; + std_thread.set_namespace(namespace_{"std"}); + std_thread.set_name("thread"); + CHECK(filter.should_include(std_thread)); + + std_thread.set_name("jthread"); + CHECK_FALSE(filter.should_include(std_thread)); + + CHECK_FALSE(filter.should_include(namespace_{"ns1::ns2::detail"})); +} + +/// +/// Main test function +/// +int main(int argc, char *argv[]) +{ + doctest::Context context; + + context.applyCommandLine(argc, argv); + + clanguml::cli::cli_handler clih; + + std::vector argvv = { + "clang-uml", "--config", "./test_config_data/simple.yml"}; + + argvv.push_back("-q"); + + clih.handle_options(argvv.size(), argvv.data()); + + int res = context.run(); + + if (context.shouldExit()) + return res; + + return res; +}