Added regex support to namespaces filter

This commit is contained in:
Bartek Kryza
2023-06-06 21:31:50 +02:00
parent 399b7e1907
commit c7e61a586b
8 changed files with 154 additions and 28 deletions

View File

@@ -173,7 +173,7 @@ tvl::value_t anyof_filter::match(
}
namespace_filter::namespace_filter(
filter_t type, std::vector<namespace_> namespaces)
filter_t type, std::vector<config::namespace_or_regex> 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<namespace_>(nsit.value())) {
const auto &ns_pattern = std::get<namespace_>(nsit.value());
return ns.starts_with(ns_pattern) || ns == ns_pattern;
}
else {
const auto &regex = std::get<config::regex>(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<const package *>(&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<namespace_>(nsit.value())) {
const auto &ns_pattern = std::get<namespace_>(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<config::regex>(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<namespace_>(nsit.value())) {
return e.get_namespace().starts_with(
std::get<namespace_>(nsit.value()));
}
else {
return std::get<config::regex>(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 {};
}

View File

@@ -112,7 +112,8 @@ private:
};
struct namespace_filter : public filter_visitor {
namespace_filter(filter_t type, std::vector<namespace_> namespaces);
namespace_filter(
filter_t type, std::vector<config::namespace_or_regex> 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<namespace_> namespaces_;
std::vector<config::namespace_or_regex> namespaces_;
};
struct element_filter : public filter_visitor {

View File

@@ -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>;
std::string to_string(string_or_regex sr);
using namespace_or_regex = std::variant<common::model::namespace_, regex>;
using namespace_or_regex = or_regex<common::model::namespace_>;
enum class method_arguments { full, abbreviated, none };
@@ -148,7 +148,7 @@ struct diagram_template {
};
struct filter {
std::vector<common::model::namespace_> namespaces;
std::vector<namespace_or_regex> namespaces;
std::vector<string_or_regex> elements;

View File

@@ -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<string_or_regex> {
}
};
template <> struct convert<namespace_or_regex> {
static bool decode(const Node &node, namespace_or_regex &rhs)
{
using namespace std::string_literals;
if (node.IsMap()) {
auto pattern = node["r"].as<std::string>();
auto rx = std::regex(pattern);
rhs = namespace_or_regex{std::move(rx), std::move(pattern)};
}
else {
rhs = namespace_or_regex{node.as<std::string>()};
}
return true;
}
};
//
// filter Yaml decoder
//
@@ -368,7 +386,7 @@ template <> struct convert<filter> {
{
if (node["namespaces"]) {
auto namespace_list =
node["namespaces"].as<std::vector<std::string>>();
node["namespaces"].as<decltype(rhs.namespaces)>();
for (const auto &ns : namespace_list)
rhs.namespaces.push_back({ns});
}

View File

@@ -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<common::model::namespace_>(m.value())) {
out << std::get<common::model::namespace_>(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)
{
out << YAML::BeginMap;

View File

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

View File

@@ -39,4 +39,13 @@ diagrams:
- r: 'ns1::.+::ns3::.+'
exclude:
elements:
- ns1::ns2::ClassZ
- ns1::ns2::ClassZ
regex_namespace_test:
type: class
include:
namespaces:
- ns1::ns2
- r: '.*interface.*'
exclude:
namespaces:
- r: '.*detail.*'

View File

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