diff --git a/docs/configuration_file.md b/docs/configuration_file.md index 087e2c8a..d8c1914a 100644 --- a/docs/configuration_file.md +++ b/docs/configuration_file.md @@ -18,12 +18,14 @@ * `elements` - list of elements, i.e. specific classes, enums, templates to include * `access` - list of visibility scopes to include (e.g. `private`) * `subclasses` - include only subclasses of specified classes (and themselves) + * `context` - include only entities in direct relationship with specified classes * `exclude` - definition of excqlusion patterns: * `namespaces` - list of namespaces to exclude * `relationships` - list of relationships to exclude * `elements` - list of elements, i.e. specific classes, enums, templates to exclude * `access` - list of visibility scopes to exclude (e.g. `private`) * `subclasses` - exclude subclasses of specified classes (and themselves) + * `context` - exclude only entities in direct relationship with specified classes * `layout` - add layout hints for entities (classes, packages) * `plantuml` - verbatim PlantUML directives which should be added to a diagram * `before` - list of directives which will be added before the generated diagram @@ -81,6 +83,9 @@ diagrams: namespaces: - clanguml::common::model - clanguml::class_diagram::model + # Only include elements in direct relationship with ClassA + context: + - ClassA exclude: # Do not include private members and methods in the diagram access: diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 2a801d87..0ae25444 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -125,6 +125,9 @@ subclass_filter::subclass_filter(filter_t type, std::vector roots) std::optional subclass_filter::match( const diagram &d, const element &e) const { + if (d.type() != diagram_t::kClass) + return {}; + if (roots_.empty()) return {}; @@ -200,8 +203,11 @@ context_filter::context_filter(filter_t type, std::vector context) } std::optional context_filter::match( - const diagram &d, const element &r) const + const diagram &d, const element &e) const { + if (d.type() != diagram_t::kClass) + return {}; + if (!d.complete()) return {}; @@ -209,7 +215,49 @@ std::optional context_filter::match( return {}; return std::any_of(context_.begin(), context_.end(), - [&r](const auto &rel) { return std::optional{}; }); + [&e, &d](const auto &context_root_name) { + const auto &context_root = + static_cast(d).get_class( + context_root_name); + + if (context_root.has_value()) { + // This is a direct match to the context root + if (context_root.value().full_name(false) == e.full_name(false)) + return true; + + // Return a positive match if the element e is in a direct + // relationship with any of the context_root's + for (const relationship &rel : + context_root.value().relationships()) { + if (rel.destination() == e.full_name(false)) + return true; + } + for (const relationship &rel : e.relationships()) { + if (rel.destination() == + context_root.value().full_name(false)) + return true; + } + + // Return a positive match if the context_root is a parent + // of the element + for (const class_diagram::model::class_parent &p : + context_root.value().parents()) { + if (p.name() == e.full_name(false)) + return true; + } + if (dynamic_cast(&e) != + nullptr) { + for (const class_diagram::model::class_parent &p : + static_cast(e) + .parents()) { + if (p.name() == context_root.value().full_name(false)) + return true; + } + } + } + + return false; + }); } diagram_filter::diagram_filter( diff --git a/tests/t00041/.clang-uml b/tests/t00041/.clang-uml new file mode 100644 index 00000000..6f49aac4 --- /dev/null +++ b/tests/t00041/.clang-uml @@ -0,0 +1,15 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00041_class: + type: class + generate_packages: false + glob: + - ../../tests/t00041/t00041.cc + using_namespace: + - clanguml::t00041 + include: + namespaces: + - clanguml::t00041 + context: + - clanguml::t00041::RR \ No newline at end of file diff --git a/tests/t00041/t00041.cc b/tests/t00041/t00041.cc new file mode 100644 index 00000000..8871f5db --- /dev/null +++ b/tests/t00041/t00041.cc @@ -0,0 +1,35 @@ +namespace clanguml::t00041 { + +struct B { +}; + +struct A { +}; + +class AA : public A { +}; + +struct R { +}; + +struct RR; + +struct D { + RR *rr; +}; + +struct E { +}; + +struct F { +}; + +struct RR : public R { + E *e; + F *f; +}; + +struct RRR : public RR { +}; + +} // namespace clanguml::t00041 diff --git a/tests/t00041/test_case.h b/tests/t00041/test_case.h new file mode 100644 index 00000000..979d93fe --- /dev/null +++ b/tests/t00041/test_case.h @@ -0,0 +1,60 @@ +/** + * tests/t00041/test_case.cc + * + * Copyright (c) 2021-2022 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("t00041", "[test-case][class]") +{ + auto [config, db] = load_config("t00041"); + + auto diagram = config.diagrams["t00041_class"]; + + REQUIRE(diagram->name == "t00041_class"); + REQUIRE(diagram->generate_packages() == false); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model->name() == "t00041_class"); + + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, !IsClass(_A("A"))); + REQUIRE_THAT(puml, !IsClass(_A("AA"))); + REQUIRE_THAT(puml, !IsClass(_A("AAA"))); + + REQUIRE_THAT(puml, !IsClass(_A("B"))); + + REQUIRE_THAT(puml, IsClass(_A("D"))); + REQUIRE_THAT(puml, IsClass(_A("E"))); + REQUIRE_THAT(puml, IsClass(_A("F"))); + REQUIRE_THAT(puml, IsClass(_A("R"))); + REQUIRE_THAT(puml, IsClass(_A("RR"))); + REQUIRE_THAT(puml, IsClass(_A("RRR"))); + + REQUIRE_THAT(puml, IsBaseClass(_A("R"), _A("RR"))); + REQUIRE_THAT(puml, IsBaseClass(_A("RR"), _A("RRR"))); + + REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("RR"), "+rr")); + REQUIRE_THAT(puml, IsAssociation(_A("RR"), _A("E"), "+e")); + REQUIRE_THAT(puml, IsAssociation(_A("RR"), _A("F"), "+f")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 4c6a175e..3487f740 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -199,6 +199,7 @@ using namespace clanguml::test::matchers; #include "t00038/test_case.h" #include "t00039/test_case.h" #include "t00040/test_case.h" +#include "t00041/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index bafd015a..ab6edccd 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -117,6 +117,9 @@ test_cases: - name: t00040 title: Relationship and access filter test description: + - name: t00041 + title: Context diagram filter test + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case