diff --git a/docs/diagram_filters.md b/docs/diagram_filters.md index 39c7d88d..548f4e71 100644 --- a/docs/diagram_filters.md +++ b/docs/diagram_filters.md @@ -7,6 +7,7 @@ * [`context`](#context) * [`relationships`](#relationships) * [`subclasses`](#subclasses) +* [`parents`](#parents) * [`specializations`](#specializations) * [`dependants` and `dependencies`](#dependants-and-dependencies) @@ -89,6 +90,10 @@ The following relationships can be used in this filter: This filter allows to include or exclude all subclasses of a given class in the diagram. +## `parents` + +This filter allows to include or exclude all parents (base classes) of a given class in the diagram. + ## `specializations` This filter allows to include or exclude specializations and instantiations of a specific template from the diagram. diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 8e04e573..e52c40d7 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -263,6 +263,45 @@ tvl::value_t subclass_filter::match(const diagram &d, const element &e) const return false; } +parents_filter::parents_filter(filter_t type, std::vector children) + : filter_visitor{type} + , children_{std::move(children)} +{ +} + +tvl::value_t parents_filter::match(const diagram &d, const element &e) const +{ + if (d.type() != diagram_t::kClass) + return {}; + + if (children_.empty()) + return {}; + + if (!d.complete()) + return {}; + + const auto &cd = dynamic_cast(d); + + // First get all parents of element e + clanguml::common::reference_set parents; + + for (const auto &child : children_) { + auto child_ref = cd.get_class(child); + if (!child_ref.has_value()) + continue; + parents.emplace(child_ref.value()); + } + + cd.get_parents(parents); + + for (const auto &parent : parents) { + if (e == parent.get()) + return true; + } + + return false; +} + relationship_filter::relationship_filter( filter_t type, std::vector relationships) : filter_visitor{type} @@ -465,6 +504,9 @@ void diagram_filter::init_filters(const config::diagram &c) element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().subclasses)); + element_filters.emplace_back(std::make_unique( + filter_t::kInclusive, c.include().parents)); + element_filters.emplace_back(std::make_unique< edge_traversal_filter>(filter_t::kInclusive, @@ -545,6 +587,9 @@ void diagram_filter::init_filters(const config::diagram &c) add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().subclasses)); + add_exclusive_filter(std::make_unique( + filter_t::kExclusive, c.exclude().parents)); + add_exclusive_filter(std::make_unique>( filter_t::kExclusive, relationship_t::kInstantiation, diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index 49e69df3..24b61955 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -135,6 +135,17 @@ private: std::vector roots_; }; +struct parents_filter : public filter_visitor { + parents_filter(filter_t type, std::vector roots); + + ~parents_filter() override = default; + + tvl::value_t match(const diagram &d, const element &e) const override; + +private: + std::vector children_; +}; + template struct edge_traversal_filter : public filter_visitor { diff --git a/src/config/config.h b/src/config/config.h index 5cfd6779..942650ff 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -69,6 +69,8 @@ struct filter { std::vector subclasses; + std::vector parents; + std::vector specializations; std::vector dependants; diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 3b4cf962..23d24ef0 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -275,6 +275,9 @@ template <> struct convert { if (node["subclasses"]) rhs.subclasses = node["subclasses"].as(); + if (node["parents"]) + rhs.parents = node["parents"].as(); + if (node["specializations"]) rhs.specializations = node["specializations"].as(); diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index b31eedf1..cb7a6be8 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -69,6 +69,8 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f) << f.specializations; if (!f.subclasses.empty()) out << YAML::Key << "subclasses" << YAML::Value << f.subclasses; + if (!f.parents.empty()) + out << YAML::Key << "parents" << YAML::Value << f.parents; out << YAML::EndMap; return out; diff --git a/tests/t00060/.clang-uml b/tests/t00060/.clang-uml new file mode 100644 index 00000000..8e127f89 --- /dev/null +++ b/tests/t00060/.clang-uml @@ -0,0 +1,15 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00060_class: + type: class + glob: + - ../../tests/t00060/t00060.cc + include: + namespaces: + - clanguml::t00060 + parents: + - clanguml::t00060::D + - clanguml::t00060::H + using_namespace: + - clanguml::t00060 \ No newline at end of file diff --git a/tests/t00060/t00060.cc b/tests/t00060/t00060.cc new file mode 100644 index 00000000..5105c041 --- /dev/null +++ b/tests/t00060/t00060.cc @@ -0,0 +1,20 @@ +namespace clanguml { +namespace t00060 { +struct A { }; +struct B : public A { }; +struct C : public A { }; +struct D : public B, public C { }; +struct E : public C { }; +struct F : public D { }; + +template struct G { + T g; +}; + +template struct H : public G { + G h; + P hh; +}; + +} +} \ No newline at end of file diff --git a/tests/t00060/test_case.h b/tests/t00060/test_case.h new file mode 100644 index 00000000..c5a94071 --- /dev/null +++ b/tests/t00060/test_case.h @@ -0,0 +1,50 @@ +/** + * tests/t00060/test_case.h + * + * Copyright (c) 2021-2023 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. + */ + +TEST_CASE("t00060", "[test-case][class]") +{ + auto [config, db] = load_config("t00060"); + + auto diagram = config.diagrams["t00060_class"]; + + REQUIRE(diagram->name == "t00060_class"); + + auto model = generate_class_diagram(*db, diagram); + + REQUIRE(model->name() == "t00060_class"); + + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all classes exist + REQUIRE_THAT(puml, IsClass(_A("A"))); + REQUIRE_THAT(puml, IsClass(_A("B"))); + REQUIRE_THAT(puml, IsClass(_A("C"))); + REQUIRE_THAT(puml, IsClass(_A("D"))); + REQUIRE_THAT(puml, !IsClass(_A("E"))); + REQUIRE_THAT(puml, !IsClass(_A("F"))); + + // Check if class templates exist + REQUIRE_THAT(puml, IsClassTemplate("G", "T")); + REQUIRE_THAT(puml, IsClassTemplate("H", "T,P")); + + save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index fa277f29..10c4bff4 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -263,6 +263,8 @@ using namespace clanguml::test::matchers; #include "t00058/test_case.h" #include "t00059/test_case.h" #endif +#include "t00060/test_case.h" + /// /// Sequence diagram tests /// diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 6466cf82..60f29e77 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -174,6 +174,9 @@ test_cases: - name: t00059 title: Non-virtual abstract factory pattern using concepts test case description: + - name: t00060 + title: Parents (base classes) diagram filter test case + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case