From 399b7e1907e475a4d0ee5f24b4fea43db04eba8e Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Mon, 5 Jun 2023 23:05:35 +0200 Subject: [PATCH] Added regex support to elements filter --- src/common/model/diagram_filter.cc | 7 +-- src/common/model/diagram_filter.h | 5 +- src/config/config.cc | 4 ++ src/config/config.h | 77 +++++++++++++++++++++++++++++- src/config/yaml_decoders.cc | 18 +++++++ src/config/yaml_emitters.cc | 13 +++++ tests/test_config_data/filters.yml | 12 ++++- tests/test_filters.cc | 41 ++++++++++++++++ 8 files changed, 170 insertions(+), 7 deletions(-) diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 2ff3801c..37fcc7bb 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -216,7 +216,8 @@ tvl::value_t namespace_filter::match( [&e](const auto &nsit) { return e.get_namespace().starts_with(nsit); }); } -element_filter::element_filter(filter_t type, std::vector elements) +element_filter::element_filter( + filter_t type, std::vector elements) : filter_visitor{type} , elements_{std::move(elements)} { @@ -227,8 +228,8 @@ tvl::value_t element_filter::match( { return tvl::any_of( elements_.begin(), elements_.end(), [&e](const auto &el) { - return (e.full_name(false) == el) || - (fmt::format("::{}", e.full_name(false)) == el); + return ((el == e.full_name(false)) || + (el == fmt::format("::{}", e.full_name(false)))); }); } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index d772602e..e710a592 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -125,14 +125,15 @@ private: }; struct element_filter : public filter_visitor { - element_filter(filter_t type, std::vector elements); + element_filter( + filter_t type, std::vector elements); ~element_filter() override = default; tvl::value_t match(const diagram &d, const element &e) const override; private: - std::vector elements_; + std::vector elements_; }; struct element_type_filter : public filter_visitor { diff --git a/src/config/config.cc b/src/config/config.cc index 95bd25bd..44ebafb0 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -24,6 +24,8 @@ namespace clanguml::config { +std::string to_string(const std::string &s) { return s; } + std::string to_string(const hint_t t) { switch (t) { @@ -85,6 +87,8 @@ std::string to_string(method_type mt) } } +std::string to_string(string_or_regex sr) { return sr.to_string(); } + std::string to_string(const comment_parser_t cp) { switch (cp) { diff --git a/src/config/config.h b/src/config/config.h index 03fa5689..8df435bc 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,80 @@ namespace clanguml { namespace config { +std::string to_string(const std::string &s); + +/** + * @brief Wrapper around std::regex, which contains original pattern + */ +struct regex { + regex(std::regex r, std::string p) + : regexp{std::move(r)} + , pattern{std::move(p)} + { + } + + [[nodiscard]] bool operator==(const std::string &v) + { + return std::regex_match(v, regexp); + } + + std::regex regexp; + std::string pattern; +}; + +template struct or_regex { + or_regex() = default; + + or_regex(T v) + : value_{std::move(v)} + { + } + + or_regex(std::regex r, std::string p) + : value_{regex{std::move(r), std::move(p)}} + { + } + + or_regex &operator=(const T &v) + { + value_ = v; + return *this; + } + + or_regex &operator=(const regex &v) + { + value_ = v; + return *this; + } + + [[nodiscard]] bool operator==(const T &v) const + { + if (std::holds_alternative(value_)) + return std::regex_match(v, std::get(value_).regexp); + + return std::get(value_) == v; + } + + std::string to_string() const + { + if (std::holds_alternative(value_)) + return std::get(value_).pattern; + + return clanguml::config::to_string(std::get(value_)); + } + + const std::variant &value() const { return value_; } + +private: + std::variant value_; +}; + +using string_or_regex = or_regex; + +std::string to_string(string_or_regex sr); + +using namespace_or_regex = std::variant; + enum class method_arguments { full, abbreviated, none }; enum class method_type { @@ -75,7 +150,7 @@ struct diagram_template { struct filter { std::vector namespaces; - std::vector elements; + std::vector elements; // E.g.: // - class diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 2ecbd1b1..6784a941 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -41,6 +41,7 @@ using clanguml::config::plantuml; using clanguml::config::relationship_hint_t; using clanguml::config::sequence_diagram; using clanguml::config::source_location; +using clanguml::config::string_or_regex; inline bool has_key(const YAML::Node &n, const std::string &key) { @@ -342,6 +343,23 @@ template <> struct convert { } }; +template <> struct convert { + static bool decode(const Node &node, string_or_regex &rhs) + { + using namespace std::string_literals; + if (node.IsMap()) { + auto pattern = node["r"].as(); + auto rx = std::regex(pattern); + rhs = string_or_regex{std::move(rx), std::move(pattern)}; + } + else { + rhs = string_or_regex{node.as()}; + } + + return true; + } +}; + // // filter Yaml decoder // diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 6685e6ca..53967d36 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -52,6 +52,19 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const method_type &m) return out; } +YAML::Emitter &operator<<(YAML::Emitter &out, const string_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/test_config_data/filters.yml b/tests/test_config_data/filters.yml index 1ebea071..13be39a6 100644 --- a/tests/test_config_data/filters.yml +++ b/tests/test_config_data/filters.yml @@ -29,4 +29,14 @@ diagrams: exclude: method_types: - deleted - - destructor \ No newline at end of file + - destructor + regex_elements_test: + type: class + include: + elements: + - ns1::ClassA + - r: 'ns1::ns2::Class.+' + - r: 'ns1::.+::ns3::.+' + exclude: + elements: + - ns1::ns2::ClassZ \ No newline at end of file diff --git a/tests/test_filters.cc b/tests/test_filters.cc index 4f1764ba..75c03741 100644 --- a/tests/test_filters.cc +++ b/tests/test_filters.cc @@ -19,6 +19,7 @@ #include "catch.h" +#include "class_diagram/model/class.h" #include "common/model/diagram_filter.h" #include "common/model/source_file.h" #include "config/config.h" @@ -103,4 +104,44 @@ TEST_CASE("Test method_types exclude filter", "[unit-test]") cm.is_destructor(true); CHECK(!filter.should_include(cm)); +} + +TEST_CASE("Test elements 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::source_file; + + using clanguml::class_diagram::model::class_; + + auto cfg = clanguml::config::load("./test_config_data/filters.yml"); + + auto &config = *cfg.diagrams["regex_elements_test"]; + clanguml::class_diagram::model::diagram diagram; + + diagram_filter filter(diagram, config); + + class_ c{{}}; + + c.set_namespace(namespace_{"ns1"}); + c.set_name("ClassA"); + + CHECK(filter.should_include(c)); + + c.set_namespace(namespace_{"ns1::ns2"}); + c.set_name("ClassA"); + + CHECK(filter.should_include(c)); + + c.set_namespace(namespace_{"ns1::ns2"}); + c.set_name("ClassZ"); + + CHECK(!filter.should_include(c)); + + c.set_namespace(namespace_{"ns1::ns5::ns3"}); + c.set_name("ClassA"); + + CHECK(filter.should_include(c)); } \ No newline at end of file