diff --git a/docs/configuration_file.md b/docs/configuration_file.md index 6bb4174f..41d4bbb8 100644 --- a/docs/configuration_file.md +++ b/docs/configuration_file.md @@ -7,20 +7,72 @@ the key of the diagram YAML node ### Diagram options -* `type` - type of diagram, one of [`class`, `sequence`] +* `type` - type of diagram, one of [`class`, `sequence`, `package`] * `glob` - list of glob patterns to match source code files for analysis * `include_relations_also_as_members` - when set to `false`, class members for relationships are rendered in UML are skipped from class definition (default: `true`) +* `generate_method_arguments` - determines whether the class diagrams methods contain full arguments (`full`), are abbreviated (`abbreviated`) or skipped (`none`) * `using_namespace` - similar to C++ `using namespace`, a `A::B` value here will render a class `A::B::C::MyClass` in the diagram as `C::MyClass` * `include` - definition of inclusion patterns: * `namespaces` - list of namespaces to include * `relationships` - list of relationships to include * `entity_types` - list of entity types to include (e.g. `class`, `enum`) * `scopes` - list of visibility scopes to include (e.g. `private`) -* `exclude` - definition of exclusion patterns: +* `exclude` - definition of excqlusion patterns: * `namespaces` - list of namespaces to exclude * `relationships` - list of relationships to exclude * `entity_types` - list of entity types to exclude (e.g. `class`, `enum`) * `scopes` - list of visibility scopes to exclude (e.g. `private`) +* `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 * `after` - list of directives which will be added after the generated diagram + + +## Example complete config + +```yaml +# Directory containing the compile_commands.json file +compilation_database_dir: debug +# The directory where *.puml files will be generated +output_directory: docs/diagrams +# Set this as default for all diagrams +generate_method_arguments: none +# The map of diagrams - keys are also diagram file names +diagrams: + main_package: + # Include this diagram definition from a separate file + include!: uml/main_package_diagram.yml + config_class: + type: class + # Do not include rendered relations in the class box + include_relations_also_as_members: false + # Limiting the number of files to include can significantly + # improve the generation time + glob: + - src/common/model/*.h + - src/common/model/*.cc + - src/class_diagram/model/*.h + - src/class_diagram/model/*.cc + include: + # Only include entities from the following namespaces + namespaces: + - clanguml::common::model + - clanguml::class_diagram::model + exclude: + # Do not include private members and methods in the diagram + scopes: + - private + layout: + # Add layout hints for PlantUML + ClassA: + - up: ClassB + - left: ClassC + # Entities from this namespace will be shortened + # (can only contain one element at the moment) + using_namespace: + - clanguml::class_diagram::model + plantuml: + # Add this line to the beginning of the resulting puml file + before: + - 'title clang-uml class diagram model' +``` \ No newline at end of file diff --git a/docs/test_cases.md b/docs/test_cases.md index 1f0e4c0d..4f2b88f4 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -33,6 +33,7 @@ * [t00032](./test_cases/t00032.md) - Class template with template base classes test case * [t00033](./test_cases/t00033.md) - Nested template instantiation dependency test case * [t00034](./test_cases/t00034.md) - Template metaprogramming type function test case + * [t00035](./test_cases/t00035.md) - PlantUML class diagram layout hints test case ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram test case * [t20002](./test_cases/t20002.md) - Free function sequence diagram test case @@ -43,5 +44,6 @@ * [t30004](./test_cases/t30004.md) - PlantUML package decorators test case * [t30005](./test_cases/t30005.md) - Package namespace alias test case * [t30006](./test_cases/t30006.md) - Package split namespace test case + * [t30007](./test_cases/t30007.md) - Package diagram layout hints test case ## Configuration diagrams * [t90000](./test_cases/t90000.md) - Basic config test diff --git a/docs/test_cases/t00006.md b/docs/test_cases/t00006.md index 7e7ade96..44b3d036 100644 --- a/docs/test_cases/t00006.md +++ b/docs/test_cases/t00006.md @@ -18,6 +18,7 @@ diagrams: ## Source code File t00006.cc ```cpp +#include #include #include diff --git a/docs/test_cases/t00017.md b/docs/test_cases/t00017.md index 503dacac..b673c436 100644 --- a/docs/test_cases/t00017.md +++ b/docs/test_cases/t00017.md @@ -19,6 +19,8 @@ diagrams: ## Source code File t00017.cc ```cpp +#include + namespace clanguml { namespace t00017 { class A { @@ -55,6 +57,15 @@ class K { }; class R { + explicit R(int &some_int, C &cc, const E &ee, F &&ff, I *&ii) + : some_int_reference{some_int} + , c{cc} + , e{ee} + , f{std::move(ff)} + , i{ii} + { + } + private: int some_int; int *some_int_pointer; @@ -64,7 +75,7 @@ private: B *b; C &c; const D *d; - const E &e{}; + const E &e; F &&f; G **g; H ***h; diff --git a/docs/test_cases/t00017_class.png b/docs/test_cases/t00017_class.png index cea9349a..b1cfc0a8 100644 Binary files a/docs/test_cases/t00017_class.png and b/docs/test_cases/t00017_class.png differ diff --git a/docs/test_cases/t00028.md b/docs/test_cases/t00028.md index 6dc6a928..b5300352 100644 --- a/docs/test_cases/t00028.md +++ b/docs/test_cases/t00028.md @@ -59,6 +59,11 @@ enum class F { one, two, three }; /// \uml{note[right] R class note.} class R { + explicit R(C &c) + : ccc(c) + { + } + A aaa; B *bbb; diff --git a/docs/test_cases/t00028_class.png b/docs/test_cases/t00028_class.png index b0fc2c7b..d11d4e73 100644 Binary files a/docs/test_cases/t00028_class.png and b/docs/test_cases/t00028_class.png differ diff --git a/docs/test_cases/t00035.md b/docs/test_cases/t00035.md new file mode 100644 index 00000000..e803b114 --- /dev/null +++ b/docs/test_cases/t00035.md @@ -0,0 +1,50 @@ +# t00035 - PlantUML class diagram layout hints test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00035_class: + type: class + glob: + - ../../tests/t00035/t00035.cc + using_namespace: + - clanguml::t00035 + include: + namespaces: + - clanguml::t00035 + layout: + Center: + - up: Top + - down: Bottom + - left: Left + - right: Right + +``` +## Source code +File t00035.cc +```cpp +namespace clanguml { +namespace t00035 { + +struct Top { +}; + +struct Left { +}; + +struct Center { +}; + +struct Bottom { +}; + +struct Right { +}; + +} // namespace t00035 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00035_class](./t00035_class.png "PlantUML class diagram layout hints test case") diff --git a/docs/test_cases/t00035_class.png b/docs/test_cases/t00035_class.png new file mode 100644 index 00000000..3acf67b1 Binary files /dev/null and b/docs/test_cases/t00035_class.png differ diff --git a/docs/test_cases/t30001_package.png b/docs/test_cases/t30001_package.png index ccd8830e..18ed09a8 100644 Binary files a/docs/test_cases/t30001_package.png and b/docs/test_cases/t30001_package.png differ diff --git a/docs/test_cases/t30002_package.png b/docs/test_cases/t30002_package.png index ce3d6e66..c8f91d6f 100644 Binary files a/docs/test_cases/t30002_package.png and b/docs/test_cases/t30002_package.png differ diff --git a/docs/test_cases/t30003_package.png b/docs/test_cases/t30003_package.png index 33e2682a..66a9b732 100644 Binary files a/docs/test_cases/t30003_package.png and b/docs/test_cases/t30003_package.png differ diff --git a/docs/test_cases/t30004_package.png b/docs/test_cases/t30004_package.png index 661b6253..31a11390 100644 Binary files a/docs/test_cases/t30004_package.png and b/docs/test_cases/t30004_package.png differ diff --git a/docs/test_cases/t30005_package.png b/docs/test_cases/t30005_package.png index b10a0efd..b97d27f1 100644 Binary files a/docs/test_cases/t30005_package.png and b/docs/test_cases/t30005_package.png differ diff --git a/docs/test_cases/t30006_package.png b/docs/test_cases/t30006_package.png index 21376db7..51f2e6ee 100644 Binary files a/docs/test_cases/t30006_package.png and b/docs/test_cases/t30006_package.png differ diff --git a/docs/test_cases/t30007.md b/docs/test_cases/t30007.md new file mode 100644 index 00000000..b2af19b5 --- /dev/null +++ b/docs/test_cases/t30007.md @@ -0,0 +1,62 @@ +# t30007 - Package diagram layout hints test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t30007_package: + type: package + glob: + - ../../tests/t30007/t30007.cc + include: + namespaces: + - clanguml::t30007 + using_namespace: + - clanguml::t30007 + layout: + C: + - up: 'A::AA' + - left: B + plantuml: + before: + - "' t30007 test package diagram" +``` +## Source code +File t30007.cc +```cpp +namespace clanguml { +namespace t30007 { + +namespace B { +struct BB { +}; +} + +/// \uml{note[top] Compare layout with t30006.} +namespace A { +namespace AA { +struct A1 { + B::BB *b; +}; +} +} + +namespace C { +struct CC { +}; +} + +/// \uml{note[bottom] Bottom A note.} +namespace A { +namespace AA { +struct A2 { + C::CC *c; +}; +} +} + +} +} +``` +## Generated UML diagrams +![t30007_package](./t30007_package.png "Package diagram layout hints test case") diff --git a/docs/test_cases/t30007_package.png b/docs/test_cases/t30007_package.png new file mode 100644 index 00000000..f97a8a02 Binary files /dev/null and b/docs/test_cases/t30007_package.png differ diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 69e911ae..3e7b906d 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -22,79 +22,9 @@ namespace clanguml::class_diagram::generators::plantuml { -std::string relative_to(std::string n, std::string c) +generator::generator(diagram_config &config, diagram_model &model) + : common_generator{config, model} { - if (c.rfind(n) == std::string::npos) - return c; - - return c.substr(n.size() + 2); -} - -generator::generator( - clanguml::config::class_diagram &config, diagram_model &model) - : m_config(config) - , m_model(model) -{ -} - -std::string generator::to_string(scope_t scope) const -{ - switch (scope) { - case scope_t::kPublic: - return "+"; - case scope_t::kProtected: - return "#"; - case scope_t::kPrivate: - return "-"; - default: - return ""; - } -} - -std::string generator::to_string(relationship_t r, std::string style) const -{ - switch (r) { - case relationship_t::kOwnership: - case relationship_t::kComposition: - return style.empty() ? "*--" : fmt::format("*-[{}]-", style); - case relationship_t::kAggregation: - return style.empty() ? "o--" : fmt::format("o-[{}]-", style); - case relationship_t::kContainment: - return style.empty() ? "--+" : fmt::format("-[{}]-+", style); - case relationship_t::kAssociation: - return style.empty() ? "-->" : fmt::format("-[{}]->", style); - case relationship_t::kInstantiation: - return style.empty() ? "..|>" : fmt::format(".[{}].|>", style); - case relationship_t::kFriendship: - return style.empty() ? "<.." : fmt::format("<.[{}].", style); - case relationship_t::kDependency: - return style.empty() ? "..>" : fmt::format(".[{}].>", style); - default: - return ""; - } -} - -std::string generator::name(relationship_t r) const -{ - switch (r) { - case relationship_t::kOwnership: - case relationship_t::kComposition: - return "composition"; - case relationship_t::kAggregation: - return "aggregation"; - case relationship_t::kContainment: - return "containment"; - case relationship_t::kAssociation: - return "association"; - case relationship_t::kInstantiation: - return "instantiation"; - case relationship_t::kFriendship: - return "friendship"; - case relationship_t::kDependency: - return "dependency"; - default: - return "unknown"; - } } void generator::generate_alias(const class_ &c, std::ostream &ostr) const @@ -118,6 +48,7 @@ void generator::generate_alias(const enum_ &e, std::ostream &ostr) const void generator::generate(const class_ &c, std::ostream &ostr) const { + namespace plantuml_common = clanguml::common::generators::plantuml; const auto &uns = m_config.using_namespace(); @@ -147,7 +78,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const std::string type{m.type()}; - ostr << to_string(m.scope()) << m.name(); + ostr << plantuml_common::to_plantuml(m.scope()) << m.name(); ostr << "("; if (m_config.generate_method_arguments() != @@ -190,10 +121,12 @@ void generator::generate(const class_ &c, std::ostream &ostr) const std::stringstream all_relations_str; std::set unique_relations; for (const auto &r : c.relationships()) { - if (!m_config.should_include_relationship(name(r.type()))) + if (!m_config.should_include_relationship( + common::model::to_string(r.type()))) continue; - LOG_DBG("== Processing relationship {}", to_string(r.type())); + LOG_DBG("== Processing relationship {}", + plantuml_common::to_plantuml(r.type(), r.style())); std::stringstream relstr; std::string destination; @@ -206,7 +139,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const if (!r.multiplicity_source().empty()) puml_relation += "\"" + r.multiplicity_source() + "\" "; - puml_relation += to_string(r.type(), r.style()); + puml_relation += plantuml_common::to_plantuml(r.type(), r.style()); if (!r.multiplicity_destination().empty()) puml_relation += " \"" + r.multiplicity_destination() + "\""; @@ -216,7 +149,8 @@ void generator::generate(const class_ &c, std::ostream &ostr) const << m_model.to_alias(ns_relative(uns, destination)); if (!r.label().empty()) { - relstr << " : " << to_string(r.scope()) << r.label(); + relstr << " : " << plantuml_common::to_plantuml(r.scope()) + << r.label(); rendered_relations.emplace(r.label()); } @@ -233,7 +167,8 @@ void generator::generate(const class_ &c, std::ostream &ostr) const catch (error::uml_alias_missing &e) { LOG_ERROR("=== Skipping {} relation from {} to {} due " "to: {}", - to_string(r.type()), c.full_name(), destination, e.what()); + plantuml_common::to_plantuml(r.type(), r.style()), + c.full_name(), destination, e.what()); } } @@ -251,13 +186,13 @@ void generator::generate(const class_ &c, std::ostream &ostr) const if (m.is_static()) ostr << "{static} "; - ostr << to_string(m.scope()) << m.name() << " : " + ostr << plantuml_common::to_plantuml(m.scope()) << m.name() << " : " << ns_relative(uns, m.type()) << '\n'; } ostr << "}" << '\n'; - if (m_config.should_include_relationship("inheritance")) + if (m_config.should_include_relationship("inheritance")) { for (const auto &b : c.parents()) { std::stringstream relstr; try { @@ -273,19 +208,10 @@ void generator::generate(const class_ &c, std::ostream &ostr) const b.name(), c.name(), e.what()); } } - - // - // Process notes - // - for (auto decorator : c.decorators()) { - auto note = std::dynamic_pointer_cast(decorator); - if (note && note->applies_to_diagram(m_config.name)) { - ostr << "note " << note->position << " of " << c.alias() << '\n' - << note->text << '\n' - << "end note\n"; - } } + generate_notes(ostr, c); + // Print relationships ostr << all_relations_str.str(); } @@ -306,18 +232,21 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const ostr << "}" << '\n'; for (const auto &r : e.relationships()) { - if (!m_config.should_include_relationship(name(r.type()))) + if (!m_config.should_include_relationship( + common::model::to_string(r.type()))) continue; std::string destination; std::stringstream relstr; try { - destination = r.destination(); relstr << m_model.to_alias( ns_relative(m_config.using_namespace(), e.name())) - << " " << to_string(r.type()) << " " + << " " + << clanguml::common::generators::plantuml::to_plantuml( + r.type(), r.style()) + << " " << m_model.to_alias( ns_relative(m_config.using_namespace(), destination)); @@ -331,38 +260,20 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const catch (error::uml_alias_missing &ex) { LOG_ERROR("Skipping {} relation from {} to {} due " "to: {}", - to_string(r.type()), e.full_name(), destination, ex.what()); + clanguml::common::generators::plantuml::to_plantuml( + r.type(), r.style()), + e.full_name(), destination, ex.what()); } } - // - // Process notes - // - for (auto decorator : e.decorators()) { - auto note = std::dynamic_pointer_cast(decorator); - if (note && note->applies_to_diagram(m_config.name)) { - ostr << "note " << note->position << " of " << e.alias() << '\n' - << note->text << '\n' - << "end note\n"; - } - } + generate_notes(ostr, e); } void generator::generate(std::ostream &ostr) const { ostr << "@startuml" << '\n'; - for (const auto &b : m_config.puml().before) { - std::string note{b}; - std::tuple alias_match; - while (util::find_element_alias(note, alias_match)) { - auto alias = m_model.to_alias(ns_relative( - m_config.using_namespace(), std::get<0>(alias_match))); - note.replace( - std::get<1>(alias_match), std::get<2>(alias_match), alias); - } - ostr << note << '\n'; - } + generate_plantuml_directives(ostr, m_config.puml().before); if (m_config.should_include_entities("classes")) { for (const auto &c : m_model.classes()) { @@ -393,57 +304,11 @@ void generator::generate(std::ostream &ostr) const ostr << '\n'; } - // Process aliases in any of the puml directives - for (const auto &b : m_config.puml().after) { - std::string note{b}; - std::tuple alias_match; - while (util::find_element_alias(note, alias_match)) { - auto alias = m_model.to_alias(ns_relative( - m_config.using_namespace(), std::get<0>(alias_match))); - note.replace( - std::get<1>(alias_match), std::get<2>(alias_match), alias); - } - ostr << note << '\n'; - } + generate_config_layout_hints(ostr); + + generate_plantuml_directives(ostr, m_config.puml().after); ostr << "@enduml" << '\n'; } -std::ostream &operator<<(std::ostream &os, const generator &g) -{ - g.generate(os); - return os; -} - -clanguml::class_diagram::model::diagram generate( - cppast::libclang_compilation_database &db, const std::string &name, - clanguml::config::class_diagram &diagram) -{ - LOG_DBG("Generating diagram {}.puml", name); - clanguml::class_diagram::model::diagram d; - d.set_name(name); - - // Get all translation units matching the glob from diagram - // configuration - std::vector translation_units{}; - for (const auto &g : diagram.glob()) { - LOG_DBG("Processing glob: {}", g); - const auto matches = glob::rglob(g); - std::copy(matches.begin(), matches.end(), - std::back_inserter(translation_units)); - } - - cppast::cpp_entity_index idx; - cppast::simple_file_parser parser{ - type_safe::ref(idx)}; - - // Process all matching translation units - clanguml::class_diagram::visitor::translation_unit_visitor ctx( - idx, d, diagram); - cppast::parse_files(parser, translation_units, db); - for (auto &file : parser.files()) - ctx(file); - - return d; -} } diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.h b/src/class_diagram/generators/plantuml/class_diagram_generator.h index 2f8388c0..ea6bf845 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.h +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.h @@ -21,6 +21,7 @@ #include "class_diagram/model/diagram.h" #include "class_diagram/model/enum.h" #include "class_diagram/visitor/translation_unit_visitor.h" +#include "common/generators/plantuml/generator.h" #include "common/model/relationship.h" #include "config/config.h" #include "cx/compilation_database.h" @@ -40,25 +41,22 @@ namespace class_diagram { namespace generators { namespace plantuml { -using diagram_config = clanguml::class_diagram::model::diagram; +using diagram_config = clanguml::config::class_diagram; using diagram_model = clanguml::class_diagram::model::diagram; +template +using common_generator = + clanguml::common::generators::plantuml::generator; + using clanguml::class_diagram::model::class_; using clanguml::class_diagram::model::enum_; using clanguml::common::model::relationship_t; using clanguml::common::model::scope_t; + using namespace clanguml::util; -std::string relative_to(std::string n, std::string c); - -class generator { +class generator : public common_generator { public: - generator(clanguml::config::class_diagram &config, diagram_model &model); - - std::string to_string(scope_t scope) const; - - std::string to_string(relationship_t r, std::string style = "") const; - - std::string name(relationship_t r) const; + generator(diagram_config &config, diagram_model &model); void generate_alias(const class_ &c, std::ostream &ostr) const; @@ -68,19 +66,9 @@ public: void generate(const enum_ &e, std::ostream &ostr) const; - void generate(std::ostream &ostr) const; - - friend std::ostream &operator<<(std::ostream &os, const generator &g); - -private: - clanguml::config::class_diagram &m_config; - diagram_model &m_model; + void generate(std::ostream &ostr) const override; }; -clanguml::class_diagram::model::diagram generate( - cppast::libclang_compilation_database &db, const std::string &name, - clanguml::config::class_diagram &diagram); - } } } diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 9eb6664b..46ec8185 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -23,10 +23,6 @@ namespace clanguml::class_diagram::model { -std::string diagram::name() const { return name_; } - -void diagram::set_name(const std::string &name) { name_ = name; } - const std::vector diagram::classes() const { return classes_; } const std::vector diagram::enums() const { return enums_; } diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index 0dffe194..7f230b02 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -18,6 +18,7 @@ #pragma once #include "class.h" +#include "common/model/diagram.h" #include "enum.h" #include "type_alias.h" @@ -26,12 +27,8 @@ namespace clanguml::class_diagram::model { -class diagram { +class diagram : public clanguml::common::model::diagram { public: - std::string name() const; - - void set_name(const std::string &name); - const std::vector classes() const; const std::vector enums() const; @@ -47,7 +44,6 @@ public: std::string to_alias(const std::string &full_name) const; private: - std::string name_; std::vector classes_; std::vector enums_; std::map type_aliases_; diff --git a/src/common/generators/plantuml/generator.cc b/src/common/generators/plantuml/generator.cc new file mode 100644 index 00000000..139a04b8 --- /dev/null +++ b/src/common/generators/plantuml/generator.cc @@ -0,0 +1,71 @@ +/** + * src/common/generators/plantuml/generator.h + * + * 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. + */ +#include "generator.h" + +namespace clanguml::common::generators::plantuml { + +std::string to_plantuml(relationship_t r, std::string style) +{ + switch (r) { + case relationship_t::kOwnership: + case relationship_t::kComposition: + return style.empty() ? "*--" : fmt::format("*-[{}]-", style); + case relationship_t::kAggregation: + return style.empty() ? "o--" : fmt::format("o-[{}]-", style); + case relationship_t::kContainment: + return style.empty() ? "--+" : fmt::format("-[{}]-+", style); + case relationship_t::kAssociation: + return style.empty() ? "-->" : fmt::format("-[{}]->", style); + case relationship_t::kInstantiation: + return style.empty() ? "..|>" : fmt::format(".[{}].|>", style); + case relationship_t::kFriendship: + return style.empty() ? "<.." : fmt::format("<.[{}].", style); + case relationship_t::kDependency: + return style.empty() ? "..>" : fmt::format(".[{}].>", style); + default: + return ""; + } +} + +std::string to_plantuml(scope_t scope) +{ + switch (scope) { + case scope_t::kPublic: + return "+"; + case scope_t::kProtected: + return "#"; + case scope_t::kPrivate: + return "-"; + default: + return ""; + } +} + +std::string to_plantuml(message_t r) +{ + switch (r) { + case message_t::kCall: + return "->"; + case message_t::kReturn: + return "-->"; + default: + return ""; + } +} + +} diff --git a/src/common/generators/plantuml/generator.h b/src/common/generators/plantuml/generator.h new file mode 100644 index 00000000..9906dbd7 --- /dev/null +++ b/src/common/generators/plantuml/generator.h @@ -0,0 +1,165 @@ +/** + * src/common/generators/plantuml/generator.h + * + * 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. + */ +#pragma once + +#include "config/config.h" +#include "util/error.h" +#include "util/util.h" + +#include +#include + +#include + +namespace clanguml::common::generators::plantuml { + +using clanguml::common::model::message_t; +using clanguml::common::model::relationship_t; +using clanguml::common::model::scope_t; + +std::string to_plantuml(relationship_t r, std::string style); +std::string to_plantuml(scope_t scope); +std::string to_plantuml(message_t r); + +template class generator { +public: + generator(ConfigType &config, DiagramType &model) + : m_config{config} + , m_model{model} + { + } + + virtual ~generator() = default; + + virtual void generate(std::ostream &ostr) const = 0; + + template + friend std::ostream &operator<<(std::ostream &os, const generator &g); + + void generate_config_layout_hints(std::ostream &ostr) const; + + void generate_plantuml_directives( + std::ostream &ostr, const std::vector &directives) const; + + void generate_notes( + std::ostream &ostr, const model::element &decorators) const; + +protected: + ConfigType &m_config; + DiagramType &m_model; +}; + +template +std::ostream &operator<<(std::ostream &os, const generator &g) +{ + g.generate(os); + return os; +} + +template +void generator::generate_config_layout_hints(std::ostream &ostr) const +{ + using namespace clanguml::util; + + const auto &uns = m_config.using_namespace(); + + // Generate layout hints + for (const auto &[entity, hints] : m_config.layout()) { + for (const auto &hint : hints) { + std::stringstream hint_str; + try { + hint_str << m_model.to_alias(ns_relative(uns, entity)) + << " -[hidden]" + << clanguml::config::to_string(hint.hint) << "- " + << m_model.to_alias(ns_relative(uns, hint.entity)) + << '\n'; + ostr << hint_str.str(); + } + catch (clanguml::error::uml_alias_missing &e) { + LOG_ERROR("=== Skipping layout hint from {} to {} due " + "to: {}", + entity, hint.entity, e.what()); + } + } + } +} + +template +void generator::generate_plantuml_directives( + std::ostream &ostr, const std::vector &directives) const +{ + for (const auto &b : directives) { + std::string note{b}; + std::tuple alias_match; + while (util::find_element_alias(note, alias_match)) { + auto alias = m_model.to_alias(util::ns_relative( + m_config.using_namespace(), std::get<0>(alias_match))); + note.replace( + std::get<1>(alias_match), std::get<2>(alias_match), alias); + } + ostr << note << '\n'; + } +} + +template +void generator::generate_notes( + std::ostream &ostr, const model::element &e) const +{ + for (auto decorator : e.decorators()) { + auto note = std::dynamic_pointer_cast(decorator); + if (note && note->applies_to_diagram(m_config.name)) { + ostr << "note " << note->position << " of " << e.alias() << '\n' + << note->text << '\n' + << "end note\n"; + } + } +} + +template +DiagramModel generate(cppast::libclang_compilation_database &db, + const std::string &name, DiagramConfig &diagram) +{ + LOG_INFO("Generating diagram {}.puml", name); + DiagramModel d; + d.set_name(name); + + // Get all translation units matching the glob from diagram + // configuration + std::vector translation_units{}; + for (const auto &g : diagram.glob()) { + LOG_DBG("Processing glob: {}", g); + const auto matches = glob::rglob(g); + std::copy(matches.begin(), matches.end(), + std::back_inserter(translation_units)); + } + + cppast::cpp_entity_index idx; + cppast::simple_file_parser parser{ + type_safe::ref(idx)}; + + // Process all matching translation units + DiagramVisitor ctx(idx, d, diagram); + cppast::parse_files(parser, translation_units, db); + for (auto &file : parser.files()) + ctx(file); + + return d; +} + +} diff --git a/src/sequence_diagram/model/enums.h b/src/common/model/diagram.cc similarity index 75% rename from src/sequence_diagram/model/enums.h rename to src/common/model/diagram.cc index 05a808e7..1f4263f5 100644 --- a/src/sequence_diagram/model/enums.h +++ b/src/common/model/diagram.cc @@ -1,5 +1,5 @@ /** - * src/sequence_diagram/model/enums.h + * src/common/model/diagram.cc * * Copyright (c) 2021-2022 Bartek Kryza * @@ -15,10 +15,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#pragma once -namespace clanguml::sequence_diagram::model { +#include "diagram.h" -enum class message_t { kCall, kReturn }; +namespace clanguml::common::model { -} +std::string diagram::name() const { return name_; } + +void diagram::set_name(const std::string &name) { name_ = name; } + +} \ No newline at end of file diff --git a/src/common/model/diagram.h b/src/common/model/diagram.h new file mode 100644 index 00000000..3e4f95b0 --- /dev/null +++ b/src/common/model/diagram.h @@ -0,0 +1,34 @@ +/** + * src/common/model/diagram.h + * + * 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. + */ +#pragma once + +#include + +namespace clanguml::common::model { + +class diagram { +public: + std::string name() const; + + void set_name(const std::string &name); + +private: + std::string name_; +}; + +} diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc new file mode 100644 index 00000000..df734e6d --- /dev/null +++ b/src/common/model/enums.cc @@ -0,0 +1,93 @@ +/** + * src/class_diagram/model/enums.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. + */ +#include "enums.h" + +#include + +namespace clanguml::common::model { + +std::string to_string(relationship_t r) +{ + switch (r) { + case relationship_t::kNone: + return "none"; + case relationship_t::kExtension: + return "extension"; + case relationship_t::kComposition: + return "composition"; + case relationship_t::kAggregation: + return "aggregation"; + case relationship_t::kContainment: + return "containment"; + case relationship_t::kOwnership: + return "ownership"; + case relationship_t::kAssociation: + return "association"; + case relationship_t::kInstantiation: + return "instantiation"; + case relationship_t::kFriendship: + return "friendship"; + case relationship_t::kDependency: + return "dependency"; + default: + assert(false); + } +} + +std::string to_string(scope_t s) +{ + switch (s) { + case scope_t::kPublic: + return "public"; + case scope_t::kProtected: + return "protected"; + case scope_t::kPrivate: + return "private"; + case scope_t::kNone: + return "none"; + default: + assert(false); + } +} + +std::string to_string(access_t a) +{ + switch (a) { + case access_t::kPublic: + return "public"; + case access_t::kProtected: + return "protected"; + case access_t::kPrivate: + return "private"; + default: + assert(false); + } +} + +std::string to_string(message_t r) +{ + switch (r) { + case message_t::kCall: + return "call"; + case message_t::kReturn: + return "return"; + default: + assert(false); + } +} +} diff --git a/src/common/model/enums.h b/src/common/model/enums.h index 08c78d45..2b553562 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -17,6 +17,8 @@ */ #pragma once +#include + namespace clanguml::common::model { enum class access_t { kPublic, kProtected, kPrivate }; @@ -36,4 +38,14 @@ enum class relationship_t { kDependency }; +enum class message_t { kCall, kReturn }; + +std::string to_string(relationship_t r); + +std::string to_string(scope_t r); + +std::string to_string(access_t r); + +std::string to_string(message_t r); + } diff --git a/src/common/model/relationship.cc b/src/common/model/relationship.cc index 96c92fd6..f384e40a 100644 --- a/src/common/model/relationship.cc +++ b/src/common/model/relationship.cc @@ -20,34 +20,6 @@ namespace clanguml::common::model { -std::string to_string(relationship_t r) -{ - switch (r) { - case relationship_t::kNone: - return "none"; - case relationship_t::kExtension: - return "extension"; - case relationship_t::kComposition: - return "composition"; - case relationship_t::kAggregation: - return "aggregation"; - case relationship_t::kContainment: - return "containment"; - case relationship_t::kOwnership: - return "ownership"; - case relationship_t::kAssociation: - return "association"; - case relationship_t::kInstantiation: - return "instantiation"; - case relationship_t::kFriendship: - return "frendship"; - case relationship_t::kDependency: - return "dependency"; - default: - return "invalid"; - } -} - relationship::relationship(relationship_t type, const std::string &destination, scope_t scope, const std::string &label, const std::string &multiplicity_source, diff --git a/src/common/model/relationship.h b/src/common/model/relationship.h index 0bb66390..d75addf8 100644 --- a/src/common/model/relationship.h +++ b/src/common/model/relationship.h @@ -24,8 +24,6 @@ namespace clanguml::common::model { -std::string to_string(relationship_t r); - class relationship : public common::model::decorated_element, public common::model::stylable_element { public: diff --git a/src/config/config.cc b/src/config/config.cc index 72a8b2f9..5489529d 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -58,6 +58,22 @@ std::string to_string(const diagram_type t) } } +std::string to_string(const hint_t t) +{ + switch (t) { + case hint_t::up: + return "up"; + case hint_t::down: + return "down"; + case hint_t::left: + return "left"; + case hint_t::right: + return "right"; + default: + assert(false); + } +} + void plantuml::append(const plantuml &r) { before.insert(before.end(), r.before.begin(), r.before.end()); @@ -204,6 +220,8 @@ using clanguml::common::model::scope_t; using clanguml::config::class_diagram; using clanguml::config::config; using clanguml::config::filter; +using clanguml::config::hint_t; +using clanguml::config::layout_hint; using clanguml::config::method_arguments; using clanguml::config::package_diagram; using clanguml::config::plantuml; @@ -375,6 +393,7 @@ template <> struct convert { return false; get_option(node, rhs.classes); + get_option(node, rhs.layout); get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.generate_method_arguments); @@ -406,6 +425,39 @@ template <> struct convert { if (!decode_diagram(node, rhs)) return false; + get_option(node, rhs.layout); + + return true; + } +}; + +// +// layout_hint Yaml decoder +// +template <> struct convert { + static bool decode(const Node &node, layout_hint &rhs) + { + assert(node.Type() == NodeType::Map); + + if (node["up"]) { + rhs.hint = hint_t::up; + rhs.entity = node["up"].as(); + } + else if (node["down"]) { + rhs.hint = hint_t::down; + rhs.entity = node["down"].as(); + } + else if (node["left"]) { + rhs.hint = hint_t::left; + rhs.entity = node["left"].as(); + } + else if (node["right"]) { + rhs.hint = hint_t::right; + rhs.entity = node["right"].as(); + } + else + return false; + return true; } }; diff --git a/src/config/config.h b/src/config/config.h index cb72d318..9ae7b614 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -65,7 +65,17 @@ struct filter { std::vector scopes; }; +enum class hint_t { up, down, left, right }; + +struct layout_hint { + hint_t hint; + std::string entity; +}; + +using layout_hints = std::map>; + std::string to_string(const diagram_type t); +std::string to_string(const hint_t t); struct inheritable_diagram_options { option> glob{"glob"}; @@ -109,6 +119,7 @@ struct class_diagram : public diagram { diagram_type type() const override; option> classes{"classes"}; + option layout{"layout"}; bool has_class(std::string clazz); }; @@ -125,6 +136,8 @@ struct package_diagram : public diagram { virtual ~package_diagram() = default; diagram_type type() const override; + + option layout{"layout"}; }; struct config : public inheritable_diagram_options { diff --git a/src/main.cc b/src/main.cc index 1b95a308..6f02be90 100644 --- a/src/main.cc +++ b/src/main.cc @@ -113,31 +113,47 @@ int main(int argc, const char *argv[]) ofs.open(path, std::ofstream::out | std::ofstream::trunc); if (diagram->type() == diagram_type::class_diagram) { + using diagram_config = clanguml::config::class_diagram; + using diagram_model = clanguml::class_diagram::model::diagram; + using diagram_visitor = + clanguml::class_diagram::visitor::translation_unit_visitor; + auto model = - clanguml::class_diagram::generators::plantuml::generate( - db, name, dynamic_cast(*diagram)); + clanguml::common::generators::plantuml::generate(db, diagram->name, + dynamic_cast(*diagram)); ofs << clanguml::class_diagram::generators::plantuml::generator( - dynamic_cast(*diagram), - model); + dynamic_cast(*diagram), model); } else if (diagram->type() == diagram_type::sequence_diagram) { + using diagram_config = clanguml::config::sequence_diagram; + using diagram_model = clanguml::sequence_diagram::model::diagram; + using diagram_visitor = + clanguml::sequence_diagram::visitor::translation_unit_visitor; + auto model = - clanguml::sequence_diagram::generators::plantuml::generate( - db, name, dynamic_cast(*diagram)); + clanguml::common::generators::plantuml::generate(db, diagram->name, + dynamic_cast(*diagram)); ofs << clanguml::sequence_diagram::generators::plantuml::generator( dynamic_cast(*diagram), model); } else if (diagram->type() == diagram_type::package_diagram) { + using diagram_config = clanguml::config::package_diagram; + using diagram_model = clanguml::package_diagram::model::diagram; + using diagram_visitor = + clanguml::package_diagram::visitor::translation_unit_visitor; + auto model = - clanguml::package_diagram::generators::plantuml::generate( - db, name, dynamic_cast(*diagram)); + clanguml::common::generators::plantuml::generate(db, diagram->name, + dynamic_cast(*diagram)); ofs << clanguml::package_diagram::generators::plantuml::generator( - dynamic_cast(*diagram), - model); + dynamic_cast(*diagram), model); } LOG_INFO("Written {} diagram to {}", name, path.string()); diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.cc b/src/package_diagram/generators/plantuml/package_diagram_generator.cc index 940a838d..f1a68570 100644 --- a/src/package_diagram/generators/plantuml/package_diagram_generator.cc +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.cc @@ -22,61 +22,9 @@ namespace clanguml::package_diagram::generators::plantuml { -std::string relative_to(std::string n, std::string c) +generator::generator(diagram_config &config, diagram_model &model) + : common_generator{config, model} { - if (c.rfind(n) == std::string::npos) - return c; - - return c.substr(n.size() + 2); -} - -generator::generator( - clanguml::config::package_diagram &config, diagram_model &model) - : m_config(config) - , m_model(model) -{ -} - -std::string generator::to_string(relationship_t r, std::string style) const -{ - switch (r) { - case relationship_t::kOwnership: - case relationship_t::kComposition: - return style.empty() ? "*--" : fmt::format("*-[{}]-", style); - case relationship_t::kAggregation: - return style.empty() ? "o--" : fmt::format("o-[{}]-", style); - case relationship_t::kContainment: - return style.empty() ? "--+" : fmt::format("-[{}]-+", style); - case relationship_t::kAssociation: - return style.empty() ? "-->" : fmt::format("-[{}]->", style); - case relationship_t::kInstantiation: - return style.empty() ? "..|>" : fmt::format(".[{}].|>", style); - case relationship_t::kFriendship: - return style.empty() ? "<.." : fmt::format("<.[{}].", style); - case relationship_t::kDependency: - return style.empty() ? "..>" : fmt::format(".[{}].>", style); - default: - return ""; - } -} - -std::string generator::name(relationship_t r) const -{ - switch (r) { - case relationship_t::kOwnership: - case relationship_t::kComposition: - return "composition"; - case relationship_t::kAggregation: - return "aggregation"; - case relationship_t::kContainment: - return "containment"; - case relationship_t::kAssociation: - return "association"; - case relationship_t::kDependency: - return "dependency"; - default: - return "unknown"; - } } void generator::generate_relationships( @@ -132,35 +80,14 @@ void generator::generate(const package &p, std::ostream &ostr) const ostr << "}" << '\n'; - // - // Process notes - // - for (auto decorator : p.decorators()) { - auto note = std::dynamic_pointer_cast(decorator); - if (note && note->applies_to_diagram(m_config.name)) { - ostr << "note " << note->position << " of " << p.alias() << '\n' - << note->text << '\n' - << "end note\n"; - } - } + generate_notes(ostr, p); } void generator::generate(std::ostream &ostr) const { ostr << "@startuml" << '\n'; - // Process aliases in any of the puml directives - for (const auto &b : m_config.puml().before) { - std::string note{b}; - std::tuple alias_match; - while (util::find_element_alias(note, alias_match)) { - auto alias = m_model.to_alias(ns_relative( - m_config.using_namespace(), std::get<0>(alias_match))); - note.replace( - std::get<1>(alias_match), std::get<2>(alias_match), alias); - } - ostr << note << '\n'; - } + generate_plantuml_directives(ostr, m_config.puml().before); if (m_config.should_include_entities("packages")) { for (const auto &p : m_model) { @@ -175,58 +102,11 @@ void generator::generate(std::ostream &ostr) const ostr << '\n'; } - // Process aliases in any of the puml directives - for (const auto &b : m_config.puml().after) { - std::string note{b}; - std::tuple alias_match; - while (util::find_element_alias(note, alias_match)) { - auto alias = m_model.to_alias(ns_relative( - m_config.using_namespace(), std::get<0>(alias_match))); - note.replace( - std::get<1>(alias_match), std::get<2>(alias_match), alias); - } - ostr << note << '\n'; - } + generate_config_layout_hints(ostr); + + generate_plantuml_directives(ostr, m_config.puml().after); ostr << "@enduml" << '\n'; } -std::ostream &operator<<(std::ostream &os, const generator &g) -{ - g.generate(os); - return os; -} - -clanguml::package_diagram::model::diagram generate( - cppast::libclang_compilation_database &db, const std::string &name, - clanguml::config::package_diagram &diagram) -{ - LOG_INFO("Generating package diagram {}.puml", name); - clanguml::package_diagram::model::diagram d; - d.set_name(name); - - // Get all translation units matching the glob from diagram - // configuration - std::vector translation_units{}; - for (const auto &g : diagram.glob()) { - LOG_DBG("Processing glob: {}", g); - const auto matches = glob::rglob(g); - std::copy(matches.begin(), matches.end(), - std::back_inserter(translation_units)); - } - - cppast::cpp_entity_index idx; - cppast::simple_file_parser parser{ - type_safe::ref(idx)}; - - // Process all matching translation units - clanguml::package_diagram::visitor::translation_unit_visitor ctx( - idx, d, diagram); - cppast::parse_files(parser, translation_units, db); - for (auto &file : parser.files()) - ctx(file); - - return d; -} - } diff --git a/src/package_diagram/generators/plantuml/package_diagram_generator.h b/src/package_diagram/generators/plantuml/package_diagram_generator.h index ed5b8fcf..a29e0743 100644 --- a/src/package_diagram/generators/plantuml/package_diagram_generator.h +++ b/src/package_diagram/generators/plantuml/package_diagram_generator.h @@ -17,6 +17,7 @@ */ #pragma once +#include "common/generators/plantuml/generator.h" #include "common/model/relationship.h" #include "config/config.h" #include "cx/compilation_database.h" @@ -27,7 +28,6 @@ #include #include -#include #include #include @@ -39,22 +39,21 @@ namespace package_diagram { namespace generators { namespace plantuml { -using diagram_config = clanguml::package_diagram::model::diagram; +using diagram_config = clanguml::config::package_diagram; using diagram_model = clanguml::package_diagram::model::diagram; + +template +using common_generator = + clanguml::common::generators::plantuml::generator; + using clanguml::common::model::relationship_t; using clanguml::common::model::scope_t; using clanguml::package_diagram::model::package; using namespace clanguml::util; -std::string relative_to(std::string n, std::string c); - -class generator { +class generator : public common_generator { public: - generator(clanguml::config::package_diagram &config, diagram_model &model); - - std::string to_string(relationship_t r, std::string style = "") const; - - std::string name(relationship_t r) const; + generator(diagram_config &config, diagram_model &model); void generate_alias(const package &c, std::ostream &ostr) const; @@ -63,18 +62,8 @@ public: void generate(const package &e, std::ostream &ostr) const; void generate(std::ostream &ostr) const; - - friend std::ostream &operator<<(std::ostream &os, const generator &g); - -private: - clanguml::config::package_diagram &m_config; - diagram_model &m_model; }; -clanguml::package_diagram::model::diagram generate( - cppast::libclang_compilation_database &db, const std::string &name, - clanguml::config::package_diagram &diagram); - } } } diff --git a/src/package_diagram/model/diagram.cc b/src/package_diagram/model/diagram.cc index 1ec49861..00a70863 100644 --- a/src/package_diagram/model/diagram.cc +++ b/src/package_diagram/model/diagram.cc @@ -23,10 +23,6 @@ namespace clanguml::package_diagram::model { -std::string diagram::name() const { return name_; } - -void diagram::set_name(const std::string &name) { name_ = name; } - std::string diagram::to_alias(const std::string &full_name) const { LOG_DBG("Looking for alias for {}", full_name); diff --git a/src/package_diagram/model/diagram.h b/src/package_diagram/model/diagram.h index 7a18f8ea..85dd70b0 100644 --- a/src/package_diagram/model/diagram.h +++ b/src/package_diagram/model/diagram.h @@ -17,6 +17,7 @@ */ #pragma once +#include "common/model/diagram.h" #include "package.h" #include @@ -26,17 +27,12 @@ namespace clanguml::package_diagram::model { -class diagram : public detail::package_trait { +class diagram : public clanguml::common::model::diagram, + public detail::package_trait { public: - std::string name() const; - - void set_name(const std::string &name); - std::string to_alias(const std::string &full_name) const; private: - std::string name_; - std::vector> packages_; }; } diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 6bb06a4c..76b6277f 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -25,12 +25,10 @@ namespace clanguml::sequence_diagram::generators::plantuml { -using diagram_model = clanguml::sequence_diagram::model::diagram; -using diagram_config = clanguml::config::sequence_diagram::diagram; +using clanguml::common::model::message_t; using clanguml::config::source_location; using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::message; -using clanguml::sequence_diagram::model::message_t; using clanguml::sequence_diagram::visitor::translation_unit_context; using namespace clanguml::util; @@ -40,31 +38,18 @@ using namespace clanguml::util; generator::generator( clanguml::config::sequence_diagram &config, diagram_model &model) - : m_config(config) - , m_model(model) + : common_generator{config, model} { } -std::string generator::to_string(message_t r) const -{ - switch (r) { - case message_t::kCall: - return "->"; - case message_t::kReturn: - return "<--"; - default: - return ""; - } -} - void generator::generate_call(const message &m, std::ostream &ostr) const { const auto from = ns_relative(m_config.using_namespace(), m.from); const auto to = ns_relative(m_config.using_namespace(), m.to); ostr << '"' << from << "\" " - << "->" - << " \"" << to << "\" : " << m.message << "()" << std::endl; + << common::generators::plantuml::to_plantuml(message_t::kCall) << " \"" + << to << "\" : " << m.message << "()" << std::endl; } void generator::generate_return(const message &m, std::ostream &ostr) const @@ -76,7 +61,7 @@ void generator::generate_return(const message &m, std::ostream &ostr) const const auto to = ns_relative(m_config.using_namespace(), m.to); ostr << '"' << to << "\" " - << "-->" + << common::generators::plantuml::to_plantuml(message_t::kReturn) << " \"" << from << "\"" << std::endl; } } @@ -98,8 +83,7 @@ void generator::generate(std::ostream &ostr) const { ostr << "@startuml" << std::endl; - for (const auto &b : m_config.puml().before) - ostr << b << std::endl; + generate_plantuml_directives(ostr, m_config.puml().before); for (const auto &sf : m_config.start_from()) { if (sf.location_type == source_location::location_t::function) { @@ -117,47 +101,10 @@ void generator::generate(std::ostream &ostr) const continue; } } - for (const auto &a : m_config.puml().after) - ostr << a << std::endl; + + generate_plantuml_directives(ostr, m_config.puml().after); ostr << "@enduml" << std::endl; } -std::ostream &operator<<(std::ostream &os, const generator &g) -{ - g.generate(os); - return os; -} - -clanguml::sequence_diagram::model::diagram generate( - cppast::libclang_compilation_database &db, const std::string &name, - clanguml::config::sequence_diagram &diagram) -{ - spdlog::info("Generating diagram {}.puml", name); - clanguml::sequence_diagram::model::diagram d; - d.name = name; - - cppast::cpp_entity_index idx; - cppast::simple_file_parser parser{ - type_safe::ref(idx)}; - - clanguml::sequence_diagram::visitor::translation_unit_visitor visitor( - idx, d, diagram); - - // Get all translation units matching the glob from diagram - // configuration - std::vector translation_units{}; - for (const auto &g : diagram.glob()) { - spdlog::debug("Processing glob: {}", g); - const auto matches = glob::rglob(g); - std::copy(matches.begin(), matches.end(), - std::back_inserter(translation_units)); - } - cppast::parse_files(parser, translation_units, db); - - for (auto &file : parser.files()) - visitor(file); - - return d; -} } diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h index f54fc42a..efcf1c0c 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h @@ -17,6 +17,7 @@ */ #pragma once +#include "common/generators/plantuml/generator.h" #include "config/config.h" #include "cx/compilation_database.h" #include "sequence_diagram/model/diagram.h" @@ -36,13 +37,16 @@ namespace sequence_diagram { namespace generators { namespace plantuml { +using diagram_config = clanguml::config::sequence_diagram; using diagram_model = clanguml::sequence_diagram::model::diagram; -class generator { -public: - generator(clanguml::config::sequence_diagram &config, diagram_model &model); +template +using common_generator = + clanguml::common::generators::plantuml::generator; - std::string to_string(clanguml::sequence_diagram::model::message_t r) const; +class generator : public common_generator { +public: + generator(diagram_config &config, diagram_model &model); void generate_call(const clanguml::sequence_diagram::model::message &m, std::ostream &ostr) const; @@ -54,18 +58,8 @@ public: std::ostream &ostr) const; void generate(std::ostream &ostr) const; - - friend std::ostream &operator<<(std::ostream &os, const generator &g); - -private: - clanguml::config::sequence_diagram &m_config; - clanguml::sequence_diagram::model::diagram &m_model; }; -clanguml::sequence_diagram::model::diagram generate( - cppast::libclang_compilation_database &db, const std::string &name, - clanguml::config::sequence_diagram &diagram); - } } } diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 6204153b..b83869ce 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -29,4 +29,9 @@ namespace clanguml::sequence_diagram::model { +std::string diagram::to_alias(const std::string &full_name) const +{ + return full_name; +} + } diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index e6ddf2cf..8cbc70eb 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -18,15 +18,18 @@ #pragma once #include "activity.h" +#include "common/model/diagram.h" #include #include namespace clanguml::sequence_diagram::model { -struct diagram { +class diagram : public clanguml::common::model::diagram { +public: + std::string to_alias(const std::string &full_name) const; + bool started{false}; - std::string name; std::map sequences; }; diff --git a/src/sequence_diagram/model/message.h b/src/sequence_diagram/model/message.h index 8c790eab..cbe7cbfd 100644 --- a/src/sequence_diagram/model/message.h +++ b/src/sequence_diagram/model/message.h @@ -17,7 +17,7 @@ */ #pragma once -#include "enums.h" +#include "common/model/enums.h" #include #include @@ -25,7 +25,7 @@ namespace clanguml::sequence_diagram::model { struct message { - message_t type; + common::model::message_t type; std::string from; std::uint_least64_t from_usr; std::string to; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 7091dafd..65244d07 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -36,10 +36,10 @@ translation_unit_visitor::translation_unit_visitor( void translation_unit_visitor::process_activities(const cppast::cpp_function &e) { + using clanguml::common::model::message_t; using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::diagram; using clanguml::sequence_diagram::model::message; - using clanguml::sequence_diagram::model::message_t; using cppast::cpp_entity; using cppast::cpp_entity_kind; using cppast::cpp_function; diff --git a/tests/t00035/.clang-uml b/tests/t00035/.clang-uml new file mode 100644 index 00000000..e2ab858d --- /dev/null +++ b/tests/t00035/.clang-uml @@ -0,0 +1,18 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00035_class: + type: class + glob: + - ../../tests/t00035/t00035.cc + using_namespace: + - clanguml::t00035 + include: + namespaces: + - clanguml::t00035 + layout: + Center: + - up: Top + - down: Bottom + - left: Left + - right: Right diff --git a/tests/t00035/t00035.cc b/tests/t00035/t00035.cc new file mode 100644 index 00000000..dbc01d47 --- /dev/null +++ b/tests/t00035/t00035.cc @@ -0,0 +1,20 @@ +namespace clanguml { +namespace t00035 { + +struct Top { +}; + +struct Left { +}; + +struct Center { +}; + +struct Bottom { +}; + +struct Right { +}; + +} // namespace t00035 +} // namespace clanguml diff --git a/tests/t00035/test_case.h b/tests/t00035/test_case.h new file mode 100644 index 00000000..7cbf4163 --- /dev/null +++ b/tests/t00035/test_case.h @@ -0,0 +1,52 @@ +/** + * tests/t00035/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("t00035", "[test-case][class]") +{ + auto [config, db] = load_config("t00035"); + + auto diagram = config.diagrams["t00035_class"]; + + REQUIRE(diagram->name == "t00035_class"); + + REQUIRE(diagram->should_include("clanguml::t00035::A")); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model.name() == "t00035_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("Top"))); + REQUIRE_THAT(puml, IsClass(_A("Bottom"))); + REQUIRE_THAT(puml, IsClass(_A("Center"))); + REQUIRE_THAT(puml, IsClass(_A("Left"))); + REQUIRE_THAT(puml, IsClass(_A("Right"))); + + REQUIRE_THAT(puml, IsLayoutHint(_A("Center"), "up", _A("Top"))); + REQUIRE_THAT(puml, IsLayoutHint(_A("Center"), "left", _A("Left"))); + REQUIRE_THAT(puml, IsLayoutHint(_A("Center"), "right", _A("Right"))); + REQUIRE_THAT(puml, IsLayoutHint(_A("Center"), "down", _A("Bottom"))); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/t20001/test_case.h b/tests/t20001/test_case.h index 1958db5c..66319027 100644 --- a/tests/t20001/test_case.h +++ b/tests/t20001/test_case.h @@ -30,7 +30,7 @@ TEST_CASE("t20001", "[test-case][sequence]") auto model = generate_sequence_diagram(db, diagram); - REQUIRE(model.name == "t20001_sequence"); + REQUIRE(model.name() == "t20001_sequence"); auto puml = generate_sequence_puml(diagram, model); diff --git a/tests/t20002/test_case.h b/tests/t20002/test_case.h index 5b4e3752..24cc8501 100644 --- a/tests/t20002/test_case.h +++ b/tests/t20002/test_case.h @@ -26,7 +26,7 @@ TEST_CASE("t20002", "[test-case][sequence]") auto model = generate_sequence_diagram(db, diagram); - REQUIRE(model.name == "t20002_sequence"); + REQUIRE(model.name() == "t20002_sequence"); auto puml = generate_sequence_puml(diagram, model); diff --git a/tests/t30007/.clang-uml b/tests/t30007/.clang-uml new file mode 100644 index 00000000..96c875a0 --- /dev/null +++ b/tests/t30007/.clang-uml @@ -0,0 +1,19 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t30007_package: + type: package + glob: + - ../../tests/t30007/t30007.cc + include: + namespaces: + - clanguml::t30007 + using_namespace: + - clanguml::t30007 + layout: + C: + - up: 'A::AA' + - left: B + plantuml: + before: + - "' t30007 test package diagram" \ No newline at end of file diff --git a/tests/t30007/t30007.cc b/tests/t30007/t30007.cc new file mode 100644 index 00000000..e79461ab --- /dev/null +++ b/tests/t30007/t30007.cc @@ -0,0 +1,33 @@ +namespace clanguml { +namespace t30007 { + +namespace B { +struct BB { +}; +} + +/// \uml{note[top] Compare layout with t30006.} +namespace A { +namespace AA { +struct A1 { + B::BB *b; +}; +} +} + +namespace C { +struct CC { +}; +} + +/// \uml{note[bottom] Bottom A note.} +namespace A { +namespace AA { +struct A2 { + C::CC *c; +}; +} +} + +} +} \ No newline at end of file diff --git a/tests/t30007/test_case.h b/tests/t30007/test_case.h new file mode 100644 index 00000000..130f1e96 --- /dev/null +++ b/tests/t30007/test_case.h @@ -0,0 +1,53 @@ +/** + * tests/t30007/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("t30007", "[test-case][package]") +{ + auto [config, db] = load_config("t30007"); + + auto diagram = config.diagrams["t30007_package"]; + + REQUIRE(diagram->should_include("clanguml::t30007::A")); + REQUIRE(diagram->should_include("clanguml::t30007::C")); + REQUIRE(!diagram->should_include("std::vector")); + + REQUIRE(diagram->name == "t30007_package"); + + auto model = generate_package_diagram(db, diagram); + + REQUIRE(model.name() == "t30007_package"); + + auto puml = generate_package_puml(diagram, model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, IsPackage("A")); + REQUIRE_THAT(puml, IsPackage("B")); + REQUIRE_THAT(puml, IsPackage("C")); + + REQUIRE_THAT(puml, IsDependency(_A("AA"), _A("B"))); + REQUIRE_THAT(puml, IsDependency(_A("AA"), _A("C"))); + + REQUIRE_THAT(puml, IsLayoutHint(_A("C"), "up", _A("AA"))); + REQUIRE_THAT(puml, IsLayoutHint(_A("C"), "left", _A("B"))); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 922e36be..30190d72 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -16,6 +16,7 @@ * limitations under the License. */ #include "test_cases.h" +#include "common/generators/plantuml/generator.h" #include @@ -44,36 +45,48 @@ clanguml::sequence_diagram::model::diagram generate_sequence_diagram( cppast::libclang_compilation_database &db, std::shared_ptr diagram) { - auto diagram_model = - clanguml::sequence_diagram::generators::plantuml::generate(db, - diagram->name, - dynamic_cast(*diagram)); + using diagram_config = clanguml::config::sequence_diagram; + using diagram_model = clanguml::sequence_diagram::model::diagram; + using diagram_visitor = + clanguml::sequence_diagram::visitor::translation_unit_visitor; - return diagram_model; + auto model = clanguml::common::generators::plantuml::generate(db, diagram->name, + dynamic_cast(*diagram)); + + return model; } clanguml::class_diagram::model::diagram generate_class_diagram( cppast::libclang_compilation_database &db, std::shared_ptr diagram) { - auto diagram_model = - clanguml::class_diagram::generators::plantuml::generate(db, - diagram->name, - dynamic_cast(*diagram)); + using diagram_config = clanguml::config::class_diagram; + using diagram_model = clanguml::class_diagram::model::diagram; + using diagram_visitor = + clanguml::class_diagram::visitor::translation_unit_visitor; - return diagram_model; + auto model = clanguml::common::generators::plantuml::generate( + db, diagram->name, dynamic_cast(*diagram)); + + return model; } clanguml::package_diagram::model::diagram generate_package_diagram( cppast::libclang_compilation_database &db, std::shared_ptr diagram) { - auto diagram_model = - clanguml::package_diagram::generators::plantuml::generate(db, - diagram->name, - dynamic_cast(*diagram)); + using diagram_config = clanguml::config::package_diagram; + using diagram_model = clanguml::package_diagram::model::diagram; + using diagram_visitor = + clanguml::package_diagram::visitor::translation_unit_visitor; - return diagram_model; + auto model = clanguml::common::generators::plantuml::generate( + db, diagram->name, dynamic_cast(*diagram)); + + return model; } std::string generate_sequence_puml( @@ -166,6 +179,7 @@ using namespace clanguml::test::matchers; #include "t00032/test_case.h" #include "t00033/test_case.h" #include "t00034/test_case.h" +#include "t00035/test_case.h" // // Sequence diagram tests @@ -182,6 +196,7 @@ using namespace clanguml::test::matchers; #include "t30004/test_case.h" #include "t30005/test_case.h" #include "t30006/test_case.h" +#include "t30007/test_case.h" // // Other tests (e.g. configuration file) diff --git a/tests/test_cases.h b/tests/test_cases.h index c18f3c5f..b20dbd8b 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -351,6 +351,14 @@ ContainsMatcher IsDependency(std::string const &from, std::string const &to, CasedString(fmt::format("{} ..> {}", from, to), caseSensitivity)); } +ContainsMatcher IsLayoutHint(std::string const &from, std::string const &hint, + std::string const &to, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return ContainsMatcher(CasedString( + fmt::format("{} -[hidden]{}- {}", from, hint, to), caseSensitivity)); +} + ContainsMatcher HasNote(std::string const &cls, std::string const &position, std::string const ¬e, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 7c3bdf30..524d2570 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -99,6 +99,9 @@ test_cases: - name: t00034 title: Template metaprogramming type function test case description: + - name: t00035 + title: PlantUML class diagram layout hints test case + description: Sequence diagrams: - name: t20001 title: Basic sequence diagram test case @@ -125,6 +128,9 @@ test_cases: - name: t30006 title: Package split namespace test case description: + - name: t30007 + title: Package diagram layout hints test case + description: Configuration diagrams: - name: t90000 title: Basic config test diff --git a/tests/test_config.cc b/tests/test_config.cc index 1a5aa603..95091f8e 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -77,4 +77,45 @@ TEST_CASE("Test config includes", "[unit-test]") CHECK(cus.include_relations_also_as_members()); CHECK(cus.generate_method_arguments() == clanguml::config::method_arguments::none); +} + +TEST_CASE("Test config layout", "[unit-test]") +{ + auto cfg = clanguml::config::load("./test_config_data/layout.yml"); + + CHECK(cfg.diagrams.size() == 2); + + auto &def = static_cast( + *cfg.diagrams["class_main"]); + + auto check_layout = [](const auto &diagram, + const clanguml::config::diagram_type type) { + CHECK(diagram.type() == type); + + CHECK(diagram.layout().at("ABCD").size() == 2); + CHECK(diagram.layout().at("ABCD")[0].hint == + clanguml::config::hint_t::up); + CHECK(diagram.layout().at("ABCD")[0].entity == "ABCD_SUBCLASS"); + CHECK(diagram.layout().at("ABCD")[1].hint == + clanguml::config::hint_t::left); + CHECK(diagram.layout().at("ABCD")[1].entity == "ABCD_SIBLING"); + + CHECK(diagram.layout().at("ABCD_SIBLING").size() == 2); + CHECK(diagram.layout().at("ABCD_SIBLING")[0].hint == + clanguml::config::hint_t::right); + CHECK(diagram.layout().at("ABCD_SIBLING")[0].entity == + "ABCD_OTHER_SIBLING"); + CHECK(diagram.layout().at("ABCD_SIBLING")[1].hint == + clanguml::config::hint_t::down); + CHECK(diagram.layout().at("ABCD_SIBLING")[1].entity == + "ABCD_SIBLING_SIBLING"); + }; + + check_layout(static_cast( + *cfg.diagrams["class_main"]), + clanguml::config::diagram_type::class_diagram); + + check_layout(static_cast( + *cfg.diagrams["package_main"]), + clanguml::config::diagram_type::package_diagram); } \ No newline at end of file diff --git a/tests/test_config_data/layout.yml b/tests/test_config_data/layout.yml new file mode 100644 index 00000000..e70502dc --- /dev/null +++ b/tests/test_config_data/layout.yml @@ -0,0 +1,37 @@ +compilation_database_dir: debug +output_directory: output +diagrams: + class_main: + type: class + glob: + - src/**/*.cc + - src/**/*.h + using_namespace: + - clanguml + generate_method_arguments: full + layout: + ABCD: + - up: ABCD_SUBCLASS + - left: ABCD_SIBLING + ABCD_SIBLING: + - right: ABCD_OTHER_SIBLING + - down: ABCD_SIBLING_SIBLING + include: + namespaces: + - clanguml + - ABCD + package_main: + type: package + glob: + - src/**/*.cc + - src/**/*.h + using_namespace: + - clanguml + generate_method_arguments: full + layout: + ABCD: + - up: ABCD_SUBCLASS + - left: ABCD_SIBLING + ABCD_SIBLING: + - right: ABCD_OTHER_SIBLING + - down: ABCD_SIBLING_SIBLING \ No newline at end of file diff --git a/uml/class_model_class_diagram.yml b/uml/class_model_class_diagram.yml index d5c70e5b..f705aca9 100644 --- a/uml/class_model_class_diagram.yml +++ b/uml/class_model_class_diagram.yml @@ -1,10 +1,14 @@ type: class include_relations_also_as_members: false +generate_method_arguments: none glob: + - src/common/model/*.h + - src/common/model/*.cc - src/class_diagram/model/*.h - src/class_diagram/model/*.cc include: namespaces: + - clanguml::common::model - clanguml::class_diagram::model using_namespace: - clanguml::class_diagram::model diff --git a/uml/diagram_model_class_diagram.yml b/uml/diagram_model_class_diagram.yml index 2264ce80..e19b60d7 100644 --- a/uml/diagram_model_class_diagram.yml +++ b/uml/diagram_model_class_diagram.yml @@ -1,5 +1,6 @@ type: class include_relations_also_as_members: false +generate_method_arguments: none glob: - src/common/model/*.h - src/common/model/*.cc diff --git a/uml/package_model_class_diagram.yml b/uml/package_model_class_diagram.yml index 4a85a917..d5648a54 100644 --- a/uml/package_model_class_diagram.yml +++ b/uml/package_model_class_diagram.yml @@ -1,10 +1,14 @@ type: class include_relations_also_as_members: false +generate_method_arguments: none glob: + - src/common/model/*.h + - src/common/model/*.cc - src/package_diagram/model/*.h - src/package_diagram/model/*.cc include: namespaces: + - clanguml::common::model - clanguml::package_diagram::model using_namespace: - clanguml::package_diagram::model diff --git a/uml/sequence_model_class_diagram.yml b/uml/sequence_model_class_diagram.yml index ed54cf72..03de4ba9 100644 --- a/uml/sequence_model_class_diagram.yml +++ b/uml/sequence_model_class_diagram.yml @@ -1,10 +1,14 @@ type: class include_relations_also_as_members: false +generate_method_arguments: none glob: + - src/common/model/*.h + - src/common/model/*.cc - src/sequence_diagram/model/*.h - src/sequence_diagram/model/*.cc include: namespaces: + - clanguml::common::model - clanguml::sequence_diagram::model using_namespace: - clanguml::sequence_diagram::model