Added regex support to elements filter

This commit is contained in:
Bartek Kryza
2023-06-05 23:05:35 +02:00
parent 3430d5422a
commit 399b7e1907
8 changed files with 170 additions and 7 deletions

View File

@@ -216,7 +216,8 @@ tvl::value_t namespace_filter::match(
[&e](const auto &nsit) { return e.get_namespace().starts_with(nsit); }); [&e](const auto &nsit) { return e.get_namespace().starts_with(nsit); });
} }
element_filter::element_filter(filter_t type, std::vector<std::string> elements) element_filter::element_filter(
filter_t type, std::vector<config::string_or_regex> elements)
: filter_visitor{type} : filter_visitor{type}
, elements_{std::move(elements)} , elements_{std::move(elements)}
{ {
@@ -227,8 +228,8 @@ tvl::value_t element_filter::match(
{ {
return tvl::any_of( return tvl::any_of(
elements_.begin(), elements_.end(), [&e](const auto &el) { elements_.begin(), elements_.end(), [&e](const auto &el) {
return (e.full_name(false) == el) || return ((el == e.full_name(false)) ||
(fmt::format("::{}", e.full_name(false)) == el); (el == fmt::format("::{}", e.full_name(false))));
}); });
} }

View File

@@ -125,14 +125,15 @@ private:
}; };
struct element_filter : public filter_visitor { struct element_filter : public filter_visitor {
element_filter(filter_t type, std::vector<std::string> elements); element_filter(
filter_t type, std::vector<config::string_or_regex> elements);
~element_filter() override = default; ~element_filter() override = default;
tvl::value_t match(const diagram &d, const element &e) const override; tvl::value_t match(const diagram &d, const element &e) const override;
private: private:
std::vector<std::string> elements_; std::vector<config::string_or_regex> elements_;
}; };
struct element_type_filter : public filter_visitor { struct element_type_filter : public filter_visitor {

View File

@@ -24,6 +24,8 @@
namespace clanguml::config { namespace clanguml::config {
std::string to_string(const std::string &s) { return s; }
std::string to_string(const hint_t t) std::string to_string(const hint_t t)
{ {
switch (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) std::string to_string(const comment_parser_t cp)
{ {
switch (cp) { switch (cp) {

View File

@@ -28,6 +28,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <regex>
#include <string> #include <string>
#include <variant> #include <variant>
#include <vector> #include <vector>
@@ -35,6 +36,80 @@
namespace clanguml { namespace clanguml {
namespace config { 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 <typename T> 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<regex>(value_))
return std::regex_match(v, std::get<regex>(value_).regexp);
return std::get<T>(value_) == v;
}
std::string to_string() const
{
if (std::holds_alternative<regex>(value_))
return std::get<regex>(value_).pattern;
return clanguml::config::to_string(std::get<T>(value_));
}
const std::variant<T, regex> &value() const { return value_; }
private:
std::variant<T, regex> value_;
};
using string_or_regex = or_regex<std::string>;
std::string to_string(string_or_regex sr);
using namespace_or_regex = std::variant<common::model::namespace_, regex>;
enum class method_arguments { full, abbreviated, none }; enum class method_arguments { full, abbreviated, none };
enum class method_type { enum class method_type {
@@ -75,7 +150,7 @@ struct diagram_template {
struct filter { struct filter {
std::vector<common::model::namespace_> namespaces; std::vector<common::model::namespace_> namespaces;
std::vector<std::string> elements; std::vector<string_or_regex> elements;
// E.g.: // E.g.:
// - class // - class

View File

@@ -41,6 +41,7 @@ using clanguml::config::plantuml;
using clanguml::config::relationship_hint_t; using clanguml::config::relationship_hint_t;
using clanguml::config::sequence_diagram; using clanguml::config::sequence_diagram;
using clanguml::config::source_location; using clanguml::config::source_location;
using clanguml::config::string_or_regex;
inline bool has_key(const YAML::Node &n, const std::string &key) inline bool has_key(const YAML::Node &n, const std::string &key)
{ {
@@ -342,6 +343,23 @@ template <> struct convert<plantuml> {
} }
}; };
template <> struct convert<string_or_regex> {
static bool decode(const Node &node, string_or_regex &rhs)
{
using namespace std::string_literals;
if (node.IsMap()) {
auto pattern = node["r"].as<std::string>();
auto rx = std::regex(pattern);
rhs = string_or_regex{std::move(rx), std::move(pattern)};
}
else {
rhs = string_or_regex{node.as<std::string>()};
}
return true;
}
};
// //
// filter Yaml decoder // filter Yaml decoder
// //

View File

@@ -52,6 +52,19 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const method_type &m)
return out; return out;
} }
YAML::Emitter &operator<<(YAML::Emitter &out, const string_or_regex &m)
{
if (std::holds_alternative<std::string>(m.value())) {
out << std::get<std::string>(m.value());
}
else {
out << YAML::Key << "r" << YAML::Value
<< std::get<regex>(m.value()).pattern;
}
return out;
}
YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f) YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f)
{ {
out << YAML::BeginMap; out << YAML::BeginMap;

View File

@@ -30,3 +30,13 @@ diagrams:
method_types: method_types:
- deleted - deleted
- destructor - destructor
regex_elements_test:
type: class
include:
elements:
- ns1::ClassA
- r: 'ns1::ns2::Class.+'
- r: 'ns1::.+::ns3::.+'
exclude:
elements:
- ns1::ns2::ClassZ

View File

@@ -19,6 +19,7 @@
#include "catch.h" #include "catch.h"
#include "class_diagram/model/class.h"
#include "common/model/diagram_filter.h" #include "common/model/diagram_filter.h"
#include "common/model/source_file.h" #include "common/model/source_file.h"
#include "config/config.h" #include "config/config.h"
@@ -104,3 +105,43 @@ TEST_CASE("Test method_types exclude filter", "[unit-test]")
CHECK(!filter.should_include(cm)); 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));
}