diff --git a/docs/test_cases.md b/docs/test_cases.md index 9b9ccb92..6e436e39 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -15,6 +15,7 @@ * [t00014](./test_cases/t00014.md) - Alias template instantiation * [t00015](./test_cases/t00015.md) - Namespace fun * [t00016](./test_cases/t00016.md) - Unnamed enums and empty templates + * [t00017](./test_cases/t00017.md) - Test include relations also as members flag ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram ## Configuration diagrams diff --git a/docs/test_cases/t00017.md b/docs/test_cases/t00017.md new file mode 100644 index 00000000..9af0d568 --- /dev/null +++ b/docs/test_cases/t00017.md @@ -0,0 +1,79 @@ +# t00017 - Test include relations also as members flag +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00017_class: + type: class + include_relations_also_as_members: false + glob: + - ../../tests/t00017/t00017.cc + using_namespace: + - clanguml::t00017 + include: + namespaces: + - clanguml::t00017 + +``` +## Source code +```cpp +namespace clanguml { +namespace t00017 { +class A { +}; + +class B { +}; + +class C { +}; + +class D { +}; + +class E { +}; + +class F { +}; + +class G { +}; + +class H { +}; + +class I { +}; + +class J { +}; + +class K { +}; + +class R { +private: + int some_int; + int *some_int_pointer; + int **some_int_pointer_pointer; + int &some_int_reference; + A a; + B *b; + C &c; + const D *d; + const E &e{}; + F &&f; + G **g; + H ***h; + I *&i; + volatile J *j; + mutable K *k; +}; +} +} + +``` +## Generated UML diagrams +![t00017_class](./t00017_class.png "Test include relations also as members flag") diff --git a/docs/test_cases/t00017_class.png b/docs/test_cases/t00017_class.png new file mode 100644 index 00000000..d7a0413d Binary files /dev/null and b/docs/test_cases/t00017_class.png differ diff --git a/src/config/config.h b/src/config/config.h index 06866cf6..d710862f 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -165,6 +165,8 @@ struct class_diagram : public diagram { virtual ~class_diagram() = default; std::vector classes; + bool include_relations_also_as_members{true}; + bool has_class(std::string clazz) { for (const auto &c : classes) { @@ -316,6 +318,11 @@ template <> struct convert { if (!decode_diagram(node, rhs)) return false; + if (node["include_relations_also_as_members"]) + rhs.include_relations_also_as_members = + node["include_relations_also_as_members"] + .as(); + return true; } }; diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index 5dc3cd28..c4625334 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -195,6 +195,56 @@ public: ostr << std::endl; } + // + // Process relationships + // + std::set rendered_relations; + + std::stringstream all_relations_str; + for (const auto &r : c.relationships) { + if (!m_config.should_include_relationship(name(r.type))) + continue; + + std::stringstream relstr; + std::string destination; + try { + if (r.destination.find("#") != std::string::npos || + r.destination.find("@") != std::string::npos) { + destination = m_model.usr_to_name(uns, r.destination); + + // If something went wrong and we have an empty destination + // generate the relationship but comment it out for + // debugging + if (destination.empty()) { + relstr << "' "; + destination = r.destination; + } + } + else { + destination = r.destination; + } + + relstr << m_model.to_alias( + uns, ns_relative(uns, c.full_name(uns))) + << " " << to_string(r.type) << " " + << m_model.to_alias(uns, ns_relative(uns, destination)); + + if (!r.label.empty()) { + relstr << " : " << to_string(r.scope) << r.label; + rendered_relations.emplace(r.label); + } + + relstr << std::endl; + + all_relations_str << relstr.str(); + } + catch (error::uml_alias_missing &e) { + LOG_ERROR("Skipping {} relation from {} to {} due " + "to: {}", + to_string(r.type), c.full_name(uns), destination, e.what()); + } + } + // // Process members // @@ -202,6 +252,10 @@ public: if (!m_config.should_include(m.scope)) continue; + if (!m_config.include_relations_also_as_members && + rendered_relations.find(m.name) != rendered_relations.end()) + continue; + if (m.is_static) ostr << "{static} "; @@ -228,47 +282,8 @@ public: } } - for (const auto &r : c.relationships) { - if (!m_config.should_include_relationship(name(r.type))) - continue; - - std::stringstream relstr; - - std::string destination; - try { - if (r.destination.find("#") != std::string::npos || - r.destination.find("@") != std::string::npos) { - destination = m_model.usr_to_name(uns, r.destination); - - // If something went wrong and we have an empty destination - // generate the relationship but comment it out for - // debugging - if (destination.empty()) { - relstr << "' "; - destination = r.destination; - } - } - else { - destination = r.destination; - } - - relstr << m_model.to_alias( - uns, ns_relative(uns, c.full_name(uns))) - << " " << to_string(r.type) << " " - << m_model.to_alias(uns, ns_relative(uns, destination)); - - if (!r.label.empty()) - relstr << " : " << to_string(r.scope) << r.label; - - relstr << std::endl; - ostr << relstr.str(); - } - catch (error::uml_alias_missing &e) { - LOG_ERROR("Skipping {} relation from {} to {} due " - "to: {}", - to_string(r.type), c.full_name(uns), destination, e.what()); - } - } + // Print relationships + ostr << all_relations_str.str(); } void generate(const enum_ &e, std::ostream &ostr) const diff --git a/tests/t00017/.clanguml b/tests/t00017/.clanguml new file mode 100644 index 00000000..e6942ec3 --- /dev/null +++ b/tests/t00017/.clanguml @@ -0,0 +1,13 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00017_class: + type: class + include_relations_also_as_members: false + glob: + - ../../tests/t00017/t00017.cc + using_namespace: + - clanguml::t00017 + include: + namespaces: + - clanguml::t00017 diff --git a/tests/t00017/t00017.cc b/tests/t00017/t00017.cc new file mode 100644 index 00000000..d10162a4 --- /dev/null +++ b/tests/t00017/t00017.cc @@ -0,0 +1,55 @@ +namespace clanguml { +namespace t00017 { +class A { +}; + +class B { +}; + +class C { +}; + +class D { +}; + +class E { +}; + +class F { +}; + +class G { +}; + +class H { +}; + +class I { +}; + +class J { +}; + +class K { +}; + +class R { +private: + int some_int; + int *some_int_pointer; + int **some_int_pointer_pointer; + int &some_int_reference; + A a; + B *b; + C &c; + const D *d; + const E &e{}; + F &&f; + G **g; + H ***h; + I *&i; + volatile J *j; + mutable K *k; +}; +} +} diff --git a/tests/t00017/test_case.h b/tests/t00017/test_case.h new file mode 100644 index 00000000..ef469653 --- /dev/null +++ b/tests/t00017/test_case.h @@ -0,0 +1,82 @@ +/** + * tests/t00017/test_case.cc + * + * Copyright (c) 2021 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("t00017", "[test-case][class]") +{ + auto [config, db] = load_config("t00017"); + + auto diagram = config.diagrams["t00017_class"]; + + REQUIRE(diagram->name == "t00017_class"); + + REQUIRE(diagram->include.namespaces.size() == 1); + REQUIRE_THAT(diagram->include.namespaces, + VectorContains(std::string{"clanguml::t00017"})); + + REQUIRE(diagram->exclude.namespaces.size() == 0); + + REQUIRE(diagram->should_include("clanguml::t00017::A")); + REQUIRE(diagram->should_include("clanguml::t00017::B")); + REQUIRE(diagram->should_include("clanguml::t00017::C")); + REQUIRE(diagram->should_include("clanguml::t00017::D")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name == "t00017_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("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"))); + REQUIRE_THAT(puml, IsClass(_A("G"))); + REQUIRE_THAT(puml, IsClass(_A("H"))); + REQUIRE_THAT(puml, IsClass(_A("I"))); + REQUIRE_THAT(puml, IsClass(_A("J"))); + REQUIRE_THAT(puml, IsClass(_A("K"))); + REQUIRE_THAT(puml, IsClass(_A("R"))); + + REQUIRE_THAT(puml, (IsField("some_int", "int"))); + REQUIRE_THAT(puml, (IsField("some_int_pointer", "int*"))); + REQUIRE_THAT(puml, (IsField("some_int_pointer_pointer", "int**"))); + + // Relationship members should not be rendered as part of this testcase + REQUIRE_THAT(puml, !(IsField("a", _A("A")))); + REQUIRE_THAT(puml, !(IsField("b", _A("B")))); + + REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("A"), "-a")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("B"), "-b")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("C"), "-c")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("D"), "-d")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("E"), "-e")); + REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("F"), "-f")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("G"), "-g")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("H"), "-h")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("I"), "-i")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("J"), "-j")); + REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("K"), "-k")); + + save_puml( + "./" + config.output_directory + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 247dd161..5cd129e8 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -120,6 +120,7 @@ using namespace clanguml::test::matchers; #include "t00014/test_case.h" #include "t00015/test_case.h" #include "t00016/test_case.h" +#include "t00017/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 12a11e65..f077977b 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -45,6 +45,9 @@ test_cases: - name: t00016 title: Unnamed enums and empty templates description: + - name: t00017 + title: Test include relations also as members flag + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram