From c7e61a586b6a00842e7ba4b325376c9f5afd7e5f Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 6 Jun 2023 21:31:50 +0200 Subject: [PATCH] Added regex support to namespaces filter --- src/common/model/diagram_filter.cc | 66 ++++++++++++++++++++++-------- src/common/model/diagram_filter.h | 5 ++- src/config/config.h | 6 +-- src/config/yaml_decoders.cc | 20 ++++++++- src/config/yaml_emitters.cc | 13 ++++++ tests/t00002/test_case.h | 3 -- tests/test_config_data/filters.yml | 11 ++++- tests/test_filters.cc | 58 ++++++++++++++++++++++++++ 8 files changed, 154 insertions(+), 28 deletions(-) diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 37fcc7bb..468b14b1 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -173,7 +173,7 @@ tvl::value_t anyof_filter::match( } namespace_filter::namespace_filter( - filter_t type, std::vector namespaces) + filter_t type, std::vector namespaces) : filter_visitor{type} , namespaces_{std::move(namespaces)} { @@ -185,8 +185,17 @@ tvl::value_t namespace_filter::match( if (ns.is_empty()) return {}; - return tvl::any_of(namespaces_.begin(), namespaces_.end(), - [&ns](const auto &nsit) { return ns.starts_with(nsit) || ns == nsit; }); + return tvl::any_of( + namespaces_.begin(), namespaces_.end(), [&ns](const auto &nsit) { + if (std::holds_alternative(nsit.value())) { + const auto &ns_pattern = std::get(nsit.value()); + return ns.starts_with(ns_pattern) || ns == ns_pattern; + } + else { + const auto ®ex = std::get(nsit.value()); + return regex == ns.to_string(); + } + }); } tvl::value_t namespace_filter::match( @@ -195,25 +204,45 @@ tvl::value_t namespace_filter::match( if (dynamic_cast(&e) != nullptr) { return tvl::any_of(namespaces_.begin(), namespaces_.end(), [&e, is_inclusive = is_inclusive()](const auto &nsit) { - auto element_full_name_starts_with_namespace = - (e.get_namespace() | e.name()).starts_with(nsit); - auto element_full_name_equals_pattern = - (e.get_namespace() | e.name()) == nsit; - auto namespace_starts_with_element_qualified_name = - nsit.starts_with(e.get_namespace()); + if (std::holds_alternative(nsit.value())) { + const auto &ns_pattern = std::get(nsit.value()); - auto result = element_full_name_starts_with_namespace || - element_full_name_equals_pattern; + auto element_full_name_starts_with_namespace = + (e.get_namespace() | e.name()).starts_with(ns_pattern); - if (is_inclusive) - result = - result || namespace_starts_with_element_qualified_name; + auto element_full_name_equals_pattern = + (e.get_namespace() | e.name()) == ns_pattern; - return result; + auto namespace_starts_with_element_qualified_name = + ns_pattern.starts_with(e.get_namespace()); + + auto result = element_full_name_starts_with_namespace || + element_full_name_equals_pattern; + + if (is_inclusive) + result = result || + namespace_starts_with_element_qualified_name; + + return result; + } + else { + return std::get(nsit.value()) == + e.full_name(false); + } }); } - return tvl::any_of(namespaces_.begin(), namespaces_.end(), - [&e](const auto &nsit) { return e.get_namespace().starts_with(nsit); }); + + return tvl::any_of( + namespaces_.begin(), namespaces_.end(), [&e](const auto &nsit) { + if (std::holds_alternative(nsit.value())) { + return e.get_namespace().starts_with( + std::get(nsit.value())); + } + else { + return std::get(nsit.value()) == + e.full_name(false); + } + }); } element_filter::element_filter( @@ -524,7 +553,8 @@ tvl::value_t paths_filter::match( auto sl_path = std::filesystem::path{p.file()}; - // Matching source paths doesn't make sens if they are not absolute or empty + // Matching source paths doesn't make sens if they are not absolute or + // empty if (p.file().empty() || sl_path.is_relative()) { return {}; } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index e710a592..61080871 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -112,7 +112,8 @@ private: }; struct namespace_filter : public filter_visitor { - namespace_filter(filter_t type, std::vector namespaces); + namespace_filter( + filter_t type, std::vector namespaces); ~namespace_filter() override = default; @@ -121,7 +122,7 @@ struct namespace_filter : public filter_visitor { tvl::value_t match(const diagram &d, const element &e) const override; private: - std::vector namespaces_; + std::vector namespaces_; }; struct element_filter : public filter_visitor { diff --git a/src/config/config.h b/src/config/config.h index 8df435bc..ee649e02 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -48,7 +48,7 @@ struct regex { { } - [[nodiscard]] bool operator==(const std::string &v) + [[nodiscard]] bool operator==(const std::string &v) const { return std::regex_match(v, regexp); } @@ -108,7 +108,7 @@ using string_or_regex = or_regex; std::string to_string(string_or_regex sr); -using namespace_or_regex = std::variant; +using namespace_or_regex = or_regex; enum class method_arguments { full, abbreviated, none }; @@ -148,7 +148,7 @@ struct diagram_template { }; struct filter { - std::vector namespaces; + std::vector namespaces; std::vector elements; diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 6784a941..b6701079 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -35,6 +35,7 @@ using clanguml::config::location_t; using clanguml::config::member_order_t; using clanguml::config::method_arguments; using clanguml::config::method_type; +using clanguml::config::namespace_or_regex; using clanguml::config::package_diagram; using clanguml::config::package_type_t; using clanguml::config::plantuml; @@ -360,6 +361,23 @@ template <> struct convert { } }; +template <> struct convert { + static bool decode(const Node &node, namespace_or_regex &rhs) + { + using namespace std::string_literals; + if (node.IsMap()) { + auto pattern = node["r"].as(); + auto rx = std::regex(pattern); + rhs = namespace_or_regex{std::move(rx), std::move(pattern)}; + } + else { + rhs = namespace_or_regex{node.as()}; + } + + return true; + } +}; + // // filter Yaml decoder // @@ -368,7 +386,7 @@ template <> struct convert { { if (node["namespaces"]) { auto namespace_list = - node["namespaces"].as>(); + node["namespaces"].as(); for (const auto &ns : namespace_list) rhs.namespaces.push_back({ns}); } diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 53967d36..20f883c4 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -65,6 +65,19 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const string_or_regex &m) return out; } +YAML::Emitter &operator<<(YAML::Emitter &out, const namespace_or_regex &m) +{ + if (std::holds_alternative(m.value())) { + out << std::get(m.value()); + } + else { + out << YAML::Key << "r" << YAML::Value + << std::get(m.value()).pattern; + } + + return out; +} + YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f) { out << YAML::BeginMap; diff --git a/tests/t00002/test_case.h b/tests/t00002/test_case.h index a4f5e69f..3b612247 100644 --- a/tests/t00002/test_case.h +++ b/tests/t00002/test_case.h @@ -25,9 +25,6 @@ TEST_CASE("t00002", "[test-case][class]") REQUIRE(diagram->name == "t00002_class"); REQUIRE(diagram->include().namespaces.size() == 1); - REQUIRE_THAT(diagram->include().namespaces, - VectorContains( - clanguml::common::model::namespace_{"clanguml::t00002"})); REQUIRE(diagram->exclude().namespaces.size() == 0); diff --git a/tests/test_config_data/filters.yml b/tests/test_config_data/filters.yml index 13be39a6..7ebf5258 100644 --- a/tests/test_config_data/filters.yml +++ b/tests/test_config_data/filters.yml @@ -39,4 +39,13 @@ diagrams: - r: 'ns1::.+::ns3::.+' exclude: elements: - - ns1::ns2::ClassZ \ No newline at end of file + - ns1::ns2::ClassZ + regex_namespace_test: + type: class + include: + namespaces: + - ns1::ns2 + - r: '.*interface.*' + exclude: + namespaces: + - r: '.*detail.*' \ No newline at end of file diff --git a/tests/test_filters.cc b/tests/test_filters.cc index 75c03741..fa1316e0 100644 --- a/tests/test_filters.cc +++ b/tests/test_filters.cc @@ -144,4 +144,62 @@ TEST_CASE("Test elements regexp filter", "[unit-test]") c.set_name("ClassA"); CHECK(filter.should_include(c)); +} + +TEST_CASE("Test namespaces regexp filter", "[unit-test]") +{ + using clanguml::class_diagram::model::class_method; + using clanguml::common::model::access_t; + using clanguml::common::model::diagram_filter; + using clanguml::common::model::namespace_; + using clanguml::common::model::package; + using clanguml::common::model::source_file; + + using clanguml::class_diagram::model::class_; + + auto cfg = clanguml::config::load("./test_config_data/filters.yml"); + + auto &config = *cfg.diagrams["regex_namespace_test"]; + clanguml::class_diagram::model::diagram diagram; + + diagram_filter filter(diagram, config); + + class_ c{{}}; + + c.set_namespace(namespace_{"ns1::ns2"}); + c.set_name("ClassA"); + + CHECK(filter.should_include(c)); + + c.set_namespace(namespace_{"ns1::ns2::detail"}); + c.set_name("ClassAImpl"); + + CHECK(!filter.should_include(c)); + + c.set_namespace(namespace_{"ns1::interface"}); + c.set_name("IClassA"); + + CHECK(filter.should_include(c)); + + CHECK(!filter.should_include(namespace_{"ns1"})); + CHECK(filter.should_include(namespace_{"ns1::ns2"})); + CHECK(!filter.should_include(namespace_{"ns1::ns2::detail"})); + CHECK(filter.should_include(namespace_{"ns1::interface"})); + + package p{{}}; + + p.set_namespace({"ns1"}); + p.set_name("ns2"); + + CHECK(filter.should_include(p)); + + p.set_namespace({"ns1::ns2"}); + p.set_name("detail"); + + CHECK(!filter.should_include(p)); + + p.set_namespace({"ns1"}); + p.set_name("interface"); + + CHECK(filter.should_include(p)); } \ No newline at end of file