Merge pull request #15 from bkryza/add-diagram-layout-hints

Add diagram layout hints
This commit is contained in:
Bartek Kryza
2022-02-16 22:14:07 +01:00
committed by GitHub
59 changed files with 1083 additions and 501 deletions

View File

@@ -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'
```

View File

@@ -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

View File

@@ -18,6 +18,7 @@ diagrams:
## Source code
File t00006.cc
```cpp
#include <array>
#include <map>
#include <vector>

View File

@@ -19,6 +19,8 @@ diagrams:
## Source code
File t00017.cc
```cpp
#include <utility>
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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 62 KiB

50
docs/test_cases/t00035.md Normal file
View File

@@ -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")

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

62
docs/test_cases/t30007.md Normal file
View File

@@ -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")

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -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<diagram_config, diagram_model>{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<std::string> 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<decorators::note>(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<decorators::note>(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<std::string, size_t, size_t> 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<std::string, size_t, size_t> 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<std::string> 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<cppast::libclang_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;
}
}

View File

@@ -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 <typename C, typename D>
using common_generator =
clanguml::common::generators::plantuml::generator<C, D>;
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<diagram_config, diagram_model> {
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);
}
}
}

View File

@@ -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<class_> diagram::classes() const { return classes_; }
const std::vector<enum_> diagram::enums() const { return enums_; }

View File

@@ -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<class_> classes() const;
const std::vector<enum_> enums() const;
@@ -47,7 +44,6 @@ public:
std::string to_alias(const std::string &full_name) const;
private:
std::string name_;
std::vector<class_> classes_;
std::vector<enum_> enums_;
std::map<std::string, type_alias> type_aliases_;

View File

@@ -0,0 +1,71 @@
/**
* src/common/generators/plantuml/generator.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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 "";
}
}
}

View File

@@ -0,0 +1,165 @@
/**
* src/common/generators/plantuml/generator.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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 <cppast/libclang_parser.hpp>
#include <glob/glob.hpp>
#include <ostream>
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 <typename ConfigType, typename DiagramType> 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 <typename C, typename D>
friend std::ostream &operator<<(std::ostream &os, const generator<C, D> &g);
void generate_config_layout_hints(std::ostream &ostr) const;
void generate_plantuml_directives(
std::ostream &ostr, const std::vector<std::string> &directives) const;
void generate_notes(
std::ostream &ostr, const model::element &decorators) const;
protected:
ConfigType &m_config;
DiagramType &m_model;
};
template <typename C, typename D>
std::ostream &operator<<(std::ostream &os, const generator<C, D> &g)
{
g.generate(os);
return os;
}
template <typename C, typename D>
void generator<C, D>::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 <typename C, typename D>
void generator<C, D>::generate_plantuml_directives(
std::ostream &ostr, const std::vector<std::string> &directives) const
{
for (const auto &b : directives) {
std::string note{b};
std::tuple<std::string, size_t, size_t> 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 <typename C, typename D>
void generator<C, D>::generate_notes(
std::ostream &ostr, const model::element &e) const
{
for (auto decorator : e.decorators()) {
auto note = std::dynamic_pointer_cast<decorators::note>(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 <typename DiagramModel, typename DiagramConfig,
typename DiagramVisitor>
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<std::string> 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<cppast::libclang_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;
}
}

View File

@@ -1,5 +1,5 @@
/**
* src/sequence_diagram/model/enums.h
* src/common/model/diagram.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
@@ -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; }
}

View File

@@ -0,0 +1,34 @@
/**
* src/common/model/diagram.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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 <string>
namespace clanguml::common::model {
class diagram {
public:
std::string name() const;
void set_name(const std::string &name);
private:
std::string name_;
};
}

93
src/common/model/enums.cc Normal file
View File

@@ -0,0 +1,93 @@
/**
* src/class_diagram/model/enums.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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 <cassert>
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);
}
}
}

View File

@@ -17,6 +17,8 @@
*/
#pragma once
#include <string>
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);
}

View File

@@ -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,

View File

@@ -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:

View File

@@ -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<class_diagram> {
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<package_diagram> {
if (!decode_diagram(node, rhs))
return false;
get_option(node, rhs.layout);
return true;
}
};
//
// layout_hint Yaml decoder
//
template <> struct convert<layout_hint> {
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<std::string>();
}
else if (node["down"]) {
rhs.hint = hint_t::down;
rhs.entity = node["down"].as<std::string>();
}
else if (node["left"]) {
rhs.hint = hint_t::left;
rhs.entity = node["left"].as<std::string>();
}
else if (node["right"]) {
rhs.hint = hint_t::right;
rhs.entity = node["right"].as<std::string>();
}
else
return false;
return true;
}
};

View File

@@ -65,7 +65,17 @@ struct filter {
std::vector<common::model::scope_t> 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, std::vector<layout_hint>>;
std::string to_string(const diagram_type t);
std::string to_string(const hint_t t);
struct inheritable_diagram_options {
option<std::vector<std::string>> glob{"glob"};
@@ -109,6 +119,7 @@ struct class_diagram : public diagram {
diagram_type type() const override;
option<std::vector<std::string>> classes{"classes"};
option<layout_hints> 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_hints> layout{"layout"};
};
struct config : public inheritable_diagram_options {

View File

@@ -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<class_diagram &>(*diagram));
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram));
ofs << clanguml::class_diagram::generators::plantuml::generator(
dynamic_cast<clanguml::config::class_diagram &>(*diagram),
model);
dynamic_cast<diagram_config &>(*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<sequence_diagram &>(*diagram));
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram));
ofs << clanguml::sequence_diagram::generators::plantuml::generator(
dynamic_cast<clanguml::config::sequence_diagram &>(*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<package_diagram &>(*diagram));
clanguml::common::generators::plantuml::generate<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<diagram_config &>(*diagram));
ofs << clanguml::package_diagram::generators::plantuml::generator(
dynamic_cast<clanguml::config::package_diagram &>(*diagram),
model);
dynamic_cast<diagram_config &>(*diagram), model);
}
LOG_INFO("Written {} diagram to {}", name, path.string());

View File

@@ -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<diagram_config, diagram_model>{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<decorators::note>(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<std::string, size_t, size_t> 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<std::string, size_t, size_t> 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<std::string> 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<cppast::libclang_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;
}
}

View File

@@ -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 <cppast/cpp_entity_index.hpp>
#include <cppast/libclang_parser.hpp>
#include <glob/glob.hpp>
#include <filesystem>
#include <fstream>
@@ -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 <typename C, typename D>
using common_generator =
clanguml::common::generators::plantuml::generator<C, D>;
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<diagram_config, diagram_model> {
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);
}
}
}

View File

@@ -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);

View File

@@ -17,6 +17,7 @@
*/
#pragma once
#include "common/model/diagram.h"
#include "package.h"
#include <type_safe/optional_ref.hpp>
@@ -26,17 +27,12 @@
namespace clanguml::package_diagram::model {
class diagram : public detail::package_trait<package, std::vector> {
class diagram : public clanguml::common::model::diagram,
public detail::package_trait<package, std::vector> {
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<std::unique_ptr<package>> packages_;
};
}

View File

@@ -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<diagram_config, diagram_model>{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<cppast::libclang_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<std::string> 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;
}
}

View File

@@ -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 <typename C, typename D>
using common_generator =
clanguml::common::generators::plantuml::generator<C, D>;
std::string to_string(clanguml::sequence_diagram::model::message_t r) const;
class generator : public common_generator<diagram_config, diagram_model> {
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);
}
}
}

View File

@@ -29,4 +29,9 @@
namespace clanguml::sequence_diagram::model {
std::string diagram::to_alias(const std::string &full_name) const
{
return full_name;
}
}

View File

@@ -18,15 +18,18 @@
#pragma once
#include "activity.h"
#include "common/model/diagram.h"
#include <map>
#include <string>
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<std::uint_least64_t, activity> sequences;
};

View File

@@ -17,7 +17,7 @@
*/
#pragma once
#include "enums.h"
#include "common/model/enums.h"
#include <string>
#include <vector>
@@ -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;

View File

@@ -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;

18
tests/t00035/.clang-uml Normal file
View File

@@ -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

20
tests/t00035/t00035.cc Normal file
View File

@@ -0,0 +1,20 @@
namespace clanguml {
namespace t00035 {
struct Top {
};
struct Left {
};
struct Center {
};
struct Bottom {
};
struct Right {
};
} // namespace t00035
} // namespace clanguml

52
tests/t00035/test_case.h Normal file
View File

@@ -0,0 +1,52 @@
/**
* tests/t00035/test_case.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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);
}

View File

@@ -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);

View File

@@ -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);

19
tests/t30007/.clang-uml Normal file
View File

@@ -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"

33
tests/t30007/t30007.cc Normal file
View File

@@ -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;
};
}
}
}
}

53
tests/t30007/test_case.h Normal file
View File

@@ -0,0 +1,53 @@
/**
* tests/t30007/test_case.cc
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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);
}

View File

@@ -16,6 +16,7 @@
* limitations under the License.
*/
#include "test_cases.h"
#include "common/generators/plantuml/generator.h"
#include <spdlog/spdlog.h>
@@ -44,36 +45,48 @@ clanguml::sequence_diagram::model::diagram generate_sequence_diagram(
cppast::libclang_compilation_database &db,
std::shared_ptr<clanguml::config::diagram> diagram)
{
auto diagram_model =
clanguml::sequence_diagram::generators::plantuml::generate(db,
diagram->name,
dynamic_cast<clanguml::config::sequence_diagram &>(*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<diagram_model,
diagram_config, diagram_visitor>(db, diagram->name,
dynamic_cast<clanguml::config::sequence_diagram &>(*diagram));
return model;
}
clanguml::class_diagram::model::diagram generate_class_diagram(
cppast::libclang_compilation_database &db,
std::shared_ptr<clanguml::config::diagram> diagram)
{
auto diagram_model =
clanguml::class_diagram::generators::plantuml::generate(db,
diagram->name,
dynamic_cast<clanguml::config::class_diagram &>(*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<diagram_model,
diagram_config, diagram_visitor>(
db, diagram->name, dynamic_cast<diagram_config &>(*diagram));
return model;
}
clanguml::package_diagram::model::diagram generate_package_diagram(
cppast::libclang_compilation_database &db,
std::shared_ptr<clanguml::config::diagram> diagram)
{
auto diagram_model =
clanguml::package_diagram::generators::plantuml::generate(db,
diagram->name,
dynamic_cast<clanguml::config::package_diagram &>(*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<diagram_model,
diagram_config, diagram_visitor>(
db, diagram->name, dynamic_cast<diagram_config &>(*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)

View File

@@ -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 &note,
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)

View File

@@ -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

View File

@@ -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<clanguml::config::class_diagram &>(
*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<clanguml::config::class_diagram &>(
*cfg.diagrams["class_main"]),
clanguml::config::diagram_type::class_diagram);
check_layout(static_cast<clanguml::config::package_diagram &>(
*cfg.diagrams["package_main"]),
clanguml::config::diagram_type::package_diagram);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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