diff --git a/docs/test_cases.md b/docs/test_cases.md index c4a69690..8f06dcc2 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -26,6 +26,10 @@ * [t00025](./test_cases/t00025.md) - Template proxy pattern * [t00026](./test_cases/t00026.md) - Template memento pattern * [t00027](./test_cases/t00027.md) - Template decorator pattern + * [t00028](./test_cases/t00028.md) - PlantUML note decorator test case + * [t00029](./test_cases/t00029.md) - PlantUML skip decorator test case + * [t00030](./test_cases/t00030.md) - PlantUML relationship decorators test case + * [t00031](./test_cases/t00031.md) - PlantUML style decorator test case ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram ## Configuration diagrams diff --git a/docs/test_cases/t00028.md b/docs/test_cases/t00028.md new file mode 100644 index 00000000..6dc6a928 --- /dev/null +++ b/docs/test_cases/t00028.md @@ -0,0 +1,80 @@ +# t00028 - PlantUML note decorator test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00028_class: + type: class + glob: + - ../../tests/t00028/t00028.cc + using_namespace: + - clanguml::t00028 + include: + namespaces: + - clanguml::t00028 + +``` +## Source code +File t00028.cc +```cpp +#include +#include + +namespace clanguml { +namespace t00028 { + +/// \uml{note[top] A class note.} +class A { +}; + +/// \uml{note[] B class note.} +class B { +}; + +/// +/// @uml{note:t00028_class[bottom] C class note.} +/// This is class C. +class C { +}; + +/// \uml{note +/// D +/// class +/// note.} +class D { +}; + +/// \uml{note E template class note.} +template class E { + T param; +}; + +/// \uml{note:other_diagram[left] G class note.} +class G { +}; + +/// @uml{note[ bottom ] F enum note.} +enum class F { one, two, three }; + +/// \uml{note[right] R class note.} +class R { + A aaa; + + B *bbb; + + C &ccc; + + std::vector> ddd; + + E eee; + + G **ggg; +}; + +} // namespace t00028 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00028_class](./t00028_class.png "PlantUML note decorator test case") diff --git a/docs/test_cases/t00028_class.png b/docs/test_cases/t00028_class.png new file mode 100644 index 00000000..9f8f8195 Binary files /dev/null and b/docs/test_cases/t00028_class.png differ diff --git a/docs/test_cases/t00029.md b/docs/test_cases/t00029.md new file mode 100644 index 00000000..e3bc3974 --- /dev/null +++ b/docs/test_cases/t00029.md @@ -0,0 +1,77 @@ +# t00029 - PlantUML skip decorator test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00029_class: + type: class + glob: + - ../../tests/t00029/t00029.cc + using_namespace: + - clanguml::t00029 + include: + namespaces: + - clanguml::t00029 + +``` +## Source code +File t00029.cc +```cpp +#include +#include + +namespace clanguml { +namespace t00029 { + +class A { +}; + +/// \uml{skip} +class B { +}; + +template class C { + T param; +}; + +/// @uml{skip:t00029_class} +template class D { + T param; +}; + +enum class E { one, two, three }; + +/// \uml{skip} +enum class F { red, green, blue }; + +class G1 { +}; + +class G2 { +}; + +class G3 { +}; + +class G4 { +}; + +struct R { + G1 g1; + + /// \uml{skip} + G2 g2; + + /// \uml{skiprelationship} + G3 &g3; + + std::shared_ptr g4; +}; + +} // namespace t00029 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00029_class](./t00029_class.png "PlantUML skip decorator test case") diff --git a/docs/test_cases/t00029_class.png b/docs/test_cases/t00029_class.png new file mode 100644 index 00000000..558f2fbf Binary files /dev/null and b/docs/test_cases/t00029_class.png differ diff --git a/docs/test_cases/t00030.md b/docs/test_cases/t00030.md new file mode 100644 index 00000000..d8634e82 --- /dev/null +++ b/docs/test_cases/t00030.md @@ -0,0 +1,58 @@ +# t00030 - PlantUML relationship decorators test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00030_class: + type: class + glob: + - ../../tests/t00030/t00030.cc + using_namespace: + - clanguml::t00030 + include: + namespaces: + - clanguml::t00030 + +``` +## Source code +File t00030.cc +```cpp +#include +#include + +namespace clanguml { +namespace t00030 { + +class A { +}; + +class B { +}; + +class C { +}; + +class D { +}; + +struct R { + /// @uml{association[]} + A aaa; + + /// @uml{composition[0..1:1..*]} + std::vector bbb; + + /// @uml{aggregation[0..1:1..5]} + std::vector ccc; + + /// @uml{association[:1]} + D ddd; +}; + +} // namespace t00030 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00030_class](./t00030_class.png "PlantUML relationship decorators test case") diff --git a/docs/test_cases/t00030_class.png b/docs/test_cases/t00030_class.png new file mode 100644 index 00000000..5d84d803 Binary files /dev/null and b/docs/test_cases/t00030_class.png differ diff --git a/docs/test_cases/t00031.md b/docs/test_cases/t00031.md new file mode 100644 index 00000000..6bbd1027 --- /dev/null +++ b/docs/test_cases/t00031.md @@ -0,0 +1,62 @@ +# t00031 - PlantUML style decorator test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00031_class: + type: class + glob: + - ../../tests/t00031/t00031.cc + using_namespace: + - clanguml::t00031 + include: + namespaces: + - clanguml::t00031 + +``` +## Source code +File t00031.cc +```cpp +#include +#include + +namespace clanguml { +namespace t00031 { + +/// @uml{style[#back:lightgreen|yellow;header:blue/red]} +class A { +}; + +/// @uml{style[#line.dotted:blue]} +enum B { one, two, three }; + +/// @uml{style[#pink;line:red;line.bold;text:red]} +template class C { + T ttt; +}; + +class D { +}; + +struct R { + /// @uml{style[#red,dashed,thickness=2]} + A *aaa; + + /// @uml{composition} + /// @uml{style[#green,dashed,thickness=4]} + std::vector bbb; + + /// @uml{style[#blue,dotted,thickness=8]} + C ccc; + + /// @uml{style[#blue,plain,thickness=16]} + D *ddd; +}; + +} // namespace t00031 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00031_class](./t00031_class.png "PlantUML style decorator test case") diff --git a/docs/test_cases/t00031_class.png b/docs/test_cases/t00031_class.png new file mode 100644 index 00000000..bf83fe3e Binary files /dev/null and b/docs/test_cases/t00031_class.png differ diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index 9acbd718..0b153195 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -75,24 +75,24 @@ public: } } - std::string to_string(relationship_t r) const + std::string to_string(relationship_t r, std::string style = "") const { switch (r) { case relationship_t::kOwnership: case relationship_t::kComposition: - return "*--"; + return style.empty() ? "*--" : fmt::format("*-[{}]-", style); case relationship_t::kAggregation: - return "o--"; + return style.empty() ? "o--" : fmt::format("o-[{}]-", style); case relationship_t::kContainment: - return "--+"; + return style.empty() ? "--+" : fmt::format("-[{}]-+", style); case relationship_t::kAssociation: - return "-->"; + return style.empty() ? "-->" : fmt::format("-[{}]->", style); case relationship_t::kInstantiation: - return "..|>"; + return style.empty() ? "..|>" : fmt::format(".[{}].|>", style); case relationship_t::kFriendship: - return "<.."; + return style.empty() ? "<.." : fmt::format("<.[{}].", style); case relationship_t::kDependency: - return "..>"; + return style.empty() ? "..>" : fmt::format(".[{}].>", style); default: return ""; } @@ -129,7 +129,7 @@ public: ostr << class_type << " \"" << c.full_name(m_config.using_namespace); - ostr << "\" as " << c.alias() << std::endl; + ostr << "\" as " << c.alias() << '\n'; } void generate_alias(const enum_ &e, std::ostream &ostr) const @@ -137,7 +137,7 @@ public: ostr << "enum" << " \"" << e.full_name(m_config.using_namespace); - ostr << "\" as " << e.alias() << std::endl; + ostr << "\" as " << e.alias() << '\n'; } void generate(const class_ &c, std::ostream &ostr) const @@ -149,7 +149,12 @@ public: if (c.is_abstract()) class_type = "abstract"; - ostr << class_type << " " << c.alias() << " {" << std::endl; + ostr << class_type << " " << c.alias(); + + if (!c.style.empty()) + ostr << " " << c.style; + + ostr << " {" << '\n'; // // Process methods @@ -192,7 +197,7 @@ public: ostr << " : " << ns_relative(uns, type); - ostr << std::endl; + ostr << '\n'; } // @@ -224,9 +229,18 @@ public: destination = r.destination; } + std::string puml_relation; + if (!r.multiplicity_source.empty()) + puml_relation += "\"" + r.multiplicity_source + "\" "; + + puml_relation += to_string(r.type, r.style); + + if (!r.multiplicity_destination.empty()) + puml_relation += " \"" + r.multiplicity_destination + "\""; + relstr << m_model.to_alias( uns, ns_relative(uns, c.full_name(uns))) - << " " << to_string(r.type) << " " + << " " << puml_relation << " " << m_model.to_alias(uns, ns_relative(uns, destination)); if (!r.label.empty()) { @@ -234,7 +248,7 @@ public: rendered_relations.emplace(r.label); } - relstr << std::endl; + relstr << '\n'; all_relations_str << relstr.str(); } @@ -260,10 +274,10 @@ public: ostr << "{static} "; ostr << to_string(m.scope) << m.name << " : " - << ns_relative(uns, m.type) << std::endl; + << ns_relative(uns, m.type) << '\n'; } - ostr << "}" << std::endl; + ostr << "}" << '\n'; if (m_config.should_include_relationship("inheritance")) for (const auto &b : c.bases) { @@ -273,7 +287,7 @@ public: << " <|-- " << m_model.to_alias( uns, ns_relative(uns, c.full_name(uns))) - << std::endl; + << '\n'; ostr << relstr.str(); } catch (error::uml_alias_missing &e) { @@ -283,19 +297,36 @@ public: } } + // + // 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"; + } + } + // Print relationships ostr << all_relations_str.str(); } void generate(const enum_ &e, std::ostream &ostr) const { - ostr << "enum " << e.alias() << " {" << std::endl; + ostr << "enum " << e.alias(); + + if (!e.style.empty()) + ostr << " " << e.style; + + ostr << " {" << '\n'; for (const auto &enum_constant : e.constants) { - ostr << enum_constant << std::endl; + ostr << enum_constant << '\n'; } - ostr << "}" << std::endl; + ostr << "}" << '\n'; for (const auto &r : e.relationships) { if (!m_config.should_include_relationship(name(r.type))) @@ -327,7 +358,8 @@ public: if (!r.label.empty()) relstr << " : " << r.label; - relstr << std::endl; + relstr << '\n'; + ostr << relstr.str(); } catch (error::uml_alias_missing &ex) { @@ -336,11 +368,23 @@ public: to_string(r.type), e.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"; + } + } } void generate(std::ostream &ostr) const { - ostr << "@startuml" << std::endl; + ostr << "@startuml" << '\n'; for (const auto &b : m_config.puml.before) { std::string note{b}; @@ -352,7 +396,7 @@ public: note.replace( std::get<1>(alias_match), std::get<2>(alias_match), alias); } - ostr << note << std::endl; + ostr << note << '\n'; } if (m_config.should_include_entities("classes")) { @@ -361,14 +405,14 @@ public: !m_config.should_include(c.name)) continue; generate_alias(c, ostr); - ostr << std::endl; + ostr << '\n'; } for (const auto &e : m_model.enums) { if (!m_config.should_include(e.name)) continue; generate_alias(e, ostr); - ostr << std::endl; + ostr << '\n'; } for (const auto &c : m_model.classes) { @@ -376,14 +420,14 @@ public: !m_config.should_include(c.name)) continue; generate(c, ostr); - ostr << std::endl; + ostr << '\n'; } } if (m_config.should_include_entities("enums")) for (const auto &e : m_model.enums) { generate(e, ostr); - ostr << std::endl; + ostr << '\n'; } // Process aliases in any of the puml directives @@ -397,10 +441,10 @@ public: note.replace( std::get<1>(alias_match), std::get<2>(alias_match), alias); } - ostr << note << std::endl; + ostr << note << '\n'; } - ostr << "@enduml" << std::endl; + ostr << "@enduml" << '\n'; } friend std::ostream &operator<<(std::ostream &os, const generator &g); diff --git a/src/uml/class_diagram_model.h b/src/uml/class_diagram_model.h index 096ba0f2..961d85a5 100644 --- a/src/uml/class_diagram_model.h +++ b/src/uml/class_diagram_model.h @@ -17,6 +17,7 @@ */ #pragma once +#include "decorators.h" #include "util/error.h" #include "util/util.h" @@ -53,7 +54,61 @@ enum class relationship_t { std::string to_string(relationship_t r); -class element { +struct stylable_element { + std::string style; +}; + +struct decorated_element { + std::vector> decorators; + + bool skip() const + { + for (auto d : decorators) + if (std::dynamic_pointer_cast(d)) + return true; + + return false; + } + + bool skip_relationship() const + { + for (auto d : decorators) + if (std::dynamic_pointer_cast(d)) + return true; + + return false; + } + + std::pair relationship() const + { + for (auto &d : decorators) + if (std::dynamic_pointer_cast(d)) + return {relationship_t::kAssociation, + std::dynamic_pointer_cast(d) + ->multiplicity}; + else if (std::dynamic_pointer_cast(d)) + return {relationship_t::kAggregation, + std::dynamic_pointer_cast(d) + ->multiplicity}; + else if (std::dynamic_pointer_cast(d)) + return {relationship_t::kComposition, + std::dynamic_pointer_cast(d) + ->multiplicity}; + + return {relationship_t::kNone, ""}; + } + + std::string style_spec() + { + for (auto d : decorators) + if (std::dynamic_pointer_cast(d)) + return std::dynamic_pointer_cast(d)->spec; + + return ""; + } +}; + +class element : public decorated_element { public: element() : m_id{m_nextId++} @@ -71,7 +126,7 @@ private: static std::atomic_uint64_t m_nextId; }; -struct class_element { +struct class_element : public decorated_element { scope_t scope; std::string name; std::string type; @@ -82,7 +137,7 @@ struct class_member : public class_element { bool is_static{false}; }; -struct method_parameter { +struct method_parameter : public decorated_element { std::string type; std::string name; std::string default_value; @@ -115,11 +170,11 @@ struct class_parent { access_t access; }; -struct class_relationship { +struct class_relationship : public decorated_element, public stylable_element { relationship_t type{relationship_t::kAssociation}; std::string destination; - std::string cardinality_source; - std::string cardinality_destination; + std::string multiplicity_source; + std::string multiplicity_destination; std::string label; scope_t scope{scope_t::kNone}; @@ -148,7 +203,7 @@ struct type_alias { std::string underlying_type; }; -class class_ : public element { +class class_ : public element, public stylable_element { public: std::string usr; bool is_struct{false}; @@ -233,7 +288,7 @@ public: } }; -struct enum_ : public element { +struct enum_ : public element, public stylable_element { std::vector constants; std::vector relationships; diff --git a/src/uml/class_diagram_visitor.cc b/src/uml/class_diagram_visitor.cc index b129b325..30c9fea8 100644 --- a/src/uml/class_diagram_visitor.cc +++ b/src/uml/class_diagram_visitor.cc @@ -193,6 +193,18 @@ void tu_visitor::process_enum_declaration(const cppast::cpp_enum &enm) enum_ e; e.name = cx::util::full_name(ctx.namespace_, enm); + if (enm.comment().has_value()) + e.decorators = decorators::parse(enm.comment().value()); + + if (e.skip()) + return; + + e.style = e.style_spec(); + + // Process enum documentation comment + if (enm.comment().has_value()) + e.decorators = decorators::parse(enm.comment().value()); + for (const auto &ev : enm) { if (ev.kind() == cppast::cpp_entity_kind::enum_value_t) { e.constants.push_back(ev.name()); @@ -226,9 +238,28 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls, c.is_struct = cls.class_kind() == cppast::cpp_class_kind::struct_t; c.name = cx::util::full_name(ctx.namespace_, cls); + if (cls.comment().has_value()) + c.decorators = decorators::parse(cls.comment().value()); + cppast::cpp_access_specifier_kind last_access_specifier = cppast::cpp_access_specifier_kind::cpp_private; + // Process class documentation comment + if (cppast::is_templated(cls)) { + if (cls.parent().value().comment().has_value()) + c.decorators = + decorators::parse(cls.parent().value().comment().value()); + } + else { + if (cls.comment().has_value()) + c.decorators = decorators::parse(cls.comment().value()); + } + + if (c.skip()) + return; + + c.style = c.style_spec(); + // Process class child entities if (c.is_struct) last_access_specifier = cppast::cpp_access_specifier_kind::cpp_public; @@ -240,7 +271,6 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls, } else if (child.kind() == cppast::cpp_entity_kind::member_variable_t) { auto &mv = static_cast(child); - LOG_DBG("Found member variable {}", mv.name()); process_field(mv, c, last_access_specifier); } else if (child.kind() == cppast::cpp_entity_kind::variable_t) { @@ -489,7 +519,7 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls, bool tu_visitor::process_field_with_template_instantiation( const cppast::cpp_member_variable &mv, const cppast::cpp_type &tr, - class_ &c, cppast::cpp_access_specifier_kind as) + class_ &c, class_member &m, cppast::cpp_access_specifier_kind as) { LOG_DBG("Processing field with template instatiation type {}", cppast::to_string(tr)); @@ -546,6 +576,17 @@ bool tu_visitor::process_field_with_template_instantiation( rr.type = relationship_t::kAggregation; rr.label = mv.name(); rr.scope = detail::cpp_access_specifier_to_scope(as); + rr.style = m.style_spec(); + + auto [decorator_rtype, decorator_rmult] = m.relationship(); + if (decorator_rtype != relationship_t::kNone) { + rr.type = decorator_rtype; + auto mult = util::split(decorator_rmult, ":"); + if (mult.size() == 2) { + rr.multiplicity_source = mult[0]; + rr.multiplicity_destination = mult[1]; + } + } LOG_DBG("Adding field instantiation relationship {} {} {} : {}", rr.destination, model::class_diagram::to_string(rr.type), c.usr, @@ -576,6 +617,12 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, m.scope = detail::cpp_access_specifier_to_scope(as); m.is_static = false; + if (mv.comment().has_value()) + m.decorators = decorators::parse(mv.comment().value()); + + if (m.skip()) + return; + auto &tr = cx::util::unreferenced(cppast::remove_cv(mv.type())); LOG_DBG("Processing field {} with unreferenced type of kind {}", mv.name(), @@ -591,7 +638,7 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) { template_instantiation_added_as_aggregation = process_field_with_template_instantiation( - mv, resolve_alias(tr), c, as); + mv, resolve_alias(tr), c, m, as); } else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) { LOG_DBG( @@ -599,7 +646,8 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, // TODO } - if (!template_instantiation_added_as_aggregation && + if (!m.skip_relationship() && + !template_instantiation_added_as_aggregation && (tr.kind() != cppast::cpp_type_kind::builtin_t) && (tr.kind() != cppast::cpp_type_kind::template_parameter_t)) { const auto &ttt = resolve_alias(mv.type()); @@ -613,6 +661,17 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c, r.type = relationship_type; r.label = m.name; r.scope = m.scope; + r.style = m.style_spec(); + + auto [decorator_rtype, decorator_rmult] = m.relationship(); + if (decorator_rtype != relationship_t::kNone) { + r.type = decorator_rtype; + auto mult = util::split(decorator_rmult, ":"); + if (mult.size() == 2) { + r.multiplicity_source = mult[0]; + r.multiplicity_destination = mult[1]; + } + } LOG_DBG("Adding field relationship {} {} {} : {}", r.destination, model::class_diagram::to_string(r.type), @@ -650,6 +709,12 @@ void tu_visitor::process_static_field(const cppast::cpp_variable &mv, class_ &c, m.scope = detail::cpp_access_specifier_to_scope(as); m.is_static = true; + if (mv.comment().has_value()) + m.decorators = decorators::parse(mv.comment().value()); + + if (m.skip()) + return; + c.members.emplace_back(std::move(m)); } @@ -666,6 +731,12 @@ void tu_visitor::process_method(const cppast::cpp_member_function &mf, m.is_static = false; m.scope = detail::cpp_access_specifier_to_scope(as); + if (mf.comment().has_value()) + m.decorators = decorators::parse(mf.comment().value()); + + if (m.skip()) + return; + for (auto ¶m : mf.parameters()) process_function_parameter(param, m, c); @@ -695,6 +766,12 @@ void tu_visitor::process_template_method( m.is_static = false; m.scope = detail::cpp_access_specifier_to_scope(as); + if (mf.comment().has_value()) + m.decorators = decorators::parse(mf.comment().value()); + + if (m.skip()) + return; + for (auto ¶m : mf.function().parameters()) process_function_parameter(param, m, c); @@ -716,6 +793,12 @@ void tu_visitor::process_static_method(const cppast::cpp_function &mf, m.is_static = true; m.scope = detail::cpp_access_specifier_to_scope(as); + if (mf.comment().has_value()) + m.decorators = decorators::parse(mf.comment().value()); + + if (m.skip()) + return; + for (auto ¶m : mf.parameters()) process_function_parameter(param, m, c); @@ -737,6 +820,12 @@ void tu_visitor::process_constructor(const cppast::cpp_constructor &mf, m.is_static = false; m.scope = detail::cpp_access_specifier_to_scope(as); + if (mf.comment().has_value()) + m.decorators = decorators::parse(mf.comment().value()); + + if (m.skip()) + return; + for (auto ¶m : mf.parameters()) process_function_parameter(param, m, c); @@ -764,6 +853,13 @@ void tu_visitor::process_function_parameter( { method_parameter mp; mp.name = param.name(); + + if (param.comment().has_value()) + m.decorators = decorators::parse(param.comment().value()); + + if (mp.skip()) + return; + const auto ¶m_type = cppast::remove_cv(cx::util::unreferenced(param.type())); if (param_type.kind() == cppast::cpp_type_kind::template_instantiation_t) { @@ -777,7 +873,7 @@ void tu_visitor::process_function_parameter( } auto dv = param.default_value(); - if (dv) + if (dv) { switch (dv.value().kind()) { case cppast::cpp_expression_kind::literal_t: mp.default_value = @@ -794,66 +890,73 @@ void tu_visitor::process_function_parameter( default: mp.default_value = "{}"; } - - // find relationship for the type - std::vector> relationships; - find_relationships(cppast::remove_cv(param.type()), relationships, - relationship_t::kDependency); - for (const auto &[type, relationship_type] : relationships) { - if ((relationship_type != relationship_t::kNone) && (type != c.name)) { - class_relationship r; - r.destination = type; - r.type = relationship_t::kDependency; - r.label = ""; - - LOG_DBG("Adding field relationship {} {} {} : {}", r.destination, - model::class_diagram::to_string(r.type), c.usr, r.label); - - c.add_relationship(std::move(r)); - } } - // Also consider the container itself if it is user defined type - const auto &t = cppast::remove_cv(cx::util::unreferenced(param.type())); - if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { - auto &template_instantiation_type = - static_cast(t); - if (template_instantiation_type.primary_template() - .get(ctx.entity_index) - .size()) { - // Here we need the name of the primary template with full - // namespace prefix to apply config inclusion filters - auto primary_template_name = cx::util::full_name(ctx.namespace_, - template_instantiation_type.primary_template() - .get(ctx.entity_index)[0] - .get()); - - LOG_DBG( - "Maybe building instantiation for: {}", primary_template_name); - - if (ctx.config.should_include(primary_template_name)) { - class_ tinst = - build_template_instantiation(template_instantiation_type); - - LOG_DBG("Created template instantiation: {}, {}", tinst.name, - tinst.usr); - + if (!mp.skip_relationship()) { + // find relationship for the type + std::vector> relationships; + find_relationships(cppast::remove_cv(param.type()), relationships, + relationship_t::kDependency); + for (const auto &[type, relationship_type] : relationships) { + if ((relationship_type != relationship_t::kNone) && + (type != c.name)) { class_relationship r; - r.destination = tinst.base_template_usr; - r.type = relationship_t::kInstantiation; + r.destination = type; + r.type = relationship_t::kDependency; r.label = ""; - tinst.add_relationship(std::move(r)); - class_relationship rr; - rr.destination = tinst.usr; - rr.type = relationship_t::kDependency; - rr.label = ""; - LOG_DBG("Adding field dependency relationship {} {} {} : {}", - rr.destination, model::class_diagram::to_string(rr.type), - c.usr, rr.label); - c.add_relationship(std::move(rr)); + LOG_DBG("Adding field relationship {} {} {} : {}", + r.destination, model::class_diagram::to_string(r.type), + c.usr, r.label); - ctx.d.add_class(std::move(tinst)); + c.add_relationship(std::move(r)); + } + } + + // Also consider the container itself if it is user defined type + const auto &t = cppast::remove_cv(cx::util::unreferenced(param.type())); + if (t.kind() == cppast::cpp_type_kind::template_instantiation_t) { + auto &template_instantiation_type = + static_cast(t); + if (template_instantiation_type.primary_template() + .get(ctx.entity_index) + .size()) { + // Here we need the name of the primary template with full + // namespace prefix to apply config inclusion filters + auto primary_template_name = cx::util::full_name(ctx.namespace_, + template_instantiation_type.primary_template() + .get(ctx.entity_index)[0] + .get()); + + LOG_DBG("Maybe building instantiation for: {}", + primary_template_name); + + if (ctx.config.should_include(primary_template_name)) { + class_ tinst = build_template_instantiation( + template_instantiation_type); + + LOG_DBG("Created template instantiation: {}, {}", + tinst.name, tinst.usr); + + class_relationship r; + r.destination = tinst.base_template_usr; + r.type = relationship_t::kInstantiation; + r.label = ""; + tinst.add_relationship(std::move(r)); + + class_relationship rr; + rr.destination = tinst.usr; + rr.type = relationship_t::kDependency; + rr.label = ""; + LOG_DBG( + "Adding field dependency relationship {} {} {} : {}", + rr.destination, + model::class_diagram::to_string(rr.type), c.usr, + rr.label); + c.add_relationship(std::move(rr)); + + ctx.d.add_class(std::move(tinst)); + } } } } @@ -910,6 +1013,12 @@ void tu_visitor::process_friend(const cppast::cpp_friend &f, class_ &parent) r.type = relationship_t::kFriendship; r.label = "<>"; + if (f.comment().has_value()) + r.decorators = decorators::parse(f.comment().value()); + + if (r.skip() || r.skip_relationship()) + return; + if (f.type()) { auto name = cppast::to_string(f.type().value()); diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 48f4ef37..f2656bf8 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -166,6 +166,7 @@ public: bool process_field_with_template_instantiation( const cppast::cpp_member_variable &mv, const cppast::cpp_type &tr, clanguml::model::class_diagram::class_ &c, + clanguml::model::class_diagram::class_member &m, cppast::cpp_access_specifier_kind as); void process_static_field(const cppast::cpp_variable &mv, diff --git a/src/uml/decorators.cc b/src/uml/decorators.cc new file mode 100644 index 00000000..23b9f8fe --- /dev/null +++ b/src/uml/decorators.cc @@ -0,0 +1,219 @@ +/** + * src/uml/decorators.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. + */ +#include "decorators.h" + +#include "util/util.h" + +#include +#include + +namespace clanguml { +namespace decorators { + +const std::string note::label = "note"; +const std::string skip::label = "skip"; +const std::string skip_relationship::label = "skiprelationship"; +const std::string style::label = "style"; +const std::string aggregation::label = "aggregation"; +const std::string composition::label = "composition"; +const std::string association::label = "association"; + +std::shared_ptr decorator::from_string(std::string_view c) +{ + if (c.find(note::label) == 0) { + return note::from_string(c); + } + else if (c.find(skip_relationship::label) == 0) { + return skip_relationship::from_string(c); + } + else if (c.find(skip::label) == 0) { + return skip::from_string(c); + } + else if (c.find(style::label) == 0) { + return style::from_string(c); + } + else if (c.find(aggregation::label) == 0) { + return aggregation::from_string(c); + } + else if (c.find(composition::label) == 0) { + return composition::from_string(c); + } + else if (c.find(association::label) == 0) { + return association::from_string(c); + } + + return {}; +} + +bool decorator::applies_to_diagram(std::string name) +{ + return diagrams.empty() || + (std::find(diagrams.begin(), diagrams.end(), name) != diagrams.end()); +} + +decorator_toks decorator::tokenize(const std::string &label, std::string_view c) +{ + decorator_toks res; + res.label = label; + size_t pos{}; + auto it = c.begin(); + std::advance(it, label.size()); + + if (*it == ':') { + std::advance(it, 1); + + pos = std::distance(c.begin(), it); + // If the diagram list is provided after ':', [] is mandatory + // even if empty + auto d = c.substr(pos, c.find("[", pos) - pos); + if (!d.empty()) { + std::string d_str{d}; + d_str.erase(std::remove_if(d_str.begin(), d_str.end(), + (int (*)(int))std::isspace), + d_str.end()); + res.diagrams = util::split(d_str, ","); + } + + std::advance(it, d.size()); + } + + if (*it == '[') { + std::advance(it, 1); + + pos = std::distance(c.begin(), it); + res.param = c.substr(pos, c.find("]", pos) - pos); + + std::advance(it, res.param.size() + 1); + } + else if (std::isspace(*it)) { + std::advance(it, 1); + } + + pos = std::distance(c.begin(), it); + res.text = c.substr(pos, c.find("}", pos) - pos); + res.text = util::trim(res.text); + res.param = util::trim(res.param); + + return res; +} + +std::shared_ptr note::from_string(std::string_view c) +{ + auto res = std::make_shared(); + auto toks = res->tokenize(note::label, c); + + res->diagrams = toks.diagrams; + + if (!toks.param.empty()) + res->position = toks.param; + + res->text = toks.text; + + return res; +} + +std::shared_ptr skip::from_string(std::string_view c) +{ + return std::make_shared(); +} + +std::shared_ptr skip_relationship::from_string(std::string_view c) +{ + return std::make_shared(); +} + +std::shared_ptr style::from_string(std::string_view c) +{ + auto res = std::make_shared