Merge pull request #16 from bkryza/enable-packages-in-class-diagrams

Enable packages in class diagrams
This commit is contained in:
Bartek Kryza
2022-02-24 23:57:28 +01:00
committed by GitHub
88 changed files with 1652 additions and 777 deletions

View File

@@ -2,11 +2,11 @@
![linux build](https://github.com/bkryza/clang-uml/actions/workflows/build.yml/badge.svg) ![linux build](https://github.com/bkryza/clang-uml/actions/workflows/build.yml/badge.svg)
`clang-uml` is an automatic C++ to [PlantUML](https://plantuml.com) class and sequence `clang-uml` is an automatic C++ to [PlantUML](https://plantuml.com) class, sequence
diagram generator, driven by YAML configuration files. The main idea behind the and package diagram generator, driven by YAML configuration files. The main idea behind the
project is to easily maintain up-to-date diagrams within a code-base or document project is to easily maintain up-to-date diagrams within a code-base or document
existing project code. The configuration file or files for `clang-uml` define the existing project code. The configuration file or files for `clang-uml` define the
type and contents of each diagram. type and contents of each generated diagram.
## Features ## Features
Main features supported so far include: Main features supported so far include:
@@ -17,11 +17,14 @@ Main features supported so far include:
* Template instantiation relationships * Template instantiation relationships
* Relationship inference from C++ containers and smart pointers * Relationship inference from C++ containers and smart pointers
* Namespace based content filtering * Namespace based content filtering
* Optional package generation from namespaces
* Sequence diagram generation * Sequence diagram generation
* Generation of sequence diagram from one code location to another * Generation of sequence diagram from one code location to another (currently only for non-template code)
* Package diagram generation * Package diagram generation
* Generation of package diagram based on C++ namespaces * Generation of package diagram based on C++ namespaces
To see what `clang-uml` can do so far, checkout the diagrams generated for unit test cases [here](./docs/test_cases.md).
## Installation ## Installation
### Building from source ### Building from source

View File

@@ -34,6 +34,7 @@
* [t00033](./test_cases/t00033.md) - Nested template instantiation dependency test case * [t00033](./test_cases/t00033.md) - Nested template instantiation dependency test case
* [t00034](./test_cases/t00034.md) - Template metaprogramming type function 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 * [t00035](./test_cases/t00035.md) - PlantUML class diagram layout hints test case
* [t00036](./test_cases/t00036.md) - Class diagram with namespaces generated as packages
## Sequence diagrams ## Sequence diagrams
* [t20001](./test_cases/t20001.md) - Basic sequence diagram test case * [t20001](./test_cases/t20001.md) - Basic sequence diagram test case
* [t20002](./test_cases/t20002.md) - Free function sequence diagram test case * [t20002](./test_cases/t20002.md) - Free function sequence diagram test case

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 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: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 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: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

57
docs/test_cases/t00036.md Normal file
View File

@@ -0,0 +1,57 @@
# t00036 - Class diagram with namespaces generated as packages
## Config
```yaml
compilation_database_dir: ..
output_directory: puml
diagrams:
t00036_class:
type: class
generate_packages: true
glob:
- ../../tests/t00036/t00036.cc
using_namespace:
- clanguml::t00036
include:
namespaces:
- clanguml::t00036
```
## Source code
File t00036.cc
```cpp
namespace clanguml {
namespace t00036 {
namespace ns1 {
enum class E { blue, yellow };
namespace ns11 {
template <typename T> struct A {
T a;
};
namespace ns111 {
struct B {
A<int> a_int;
};
}
}
}
namespace ns2 {
namespace ns22 {
struct C;
}
}
} // namespace t00036
} // namespace clanguml
```
## Generated UML diagrams
![t00036_class](./t00036_class.png "Class diagram with namespaces generated as packages")

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -33,6 +33,11 @@ void generator::generate_alias(const class_ &c, std::ostream &ostr) const
if (c.is_abstract()) if (c.is_abstract())
class_type = "abstract"; class_type = "abstract";
auto full_name = c.full_name();
if (m_config.generate_packages())
ostr << class_type << " \"" << c.full_name_no_ns();
else
ostr << class_type << " \"" << c.full_name(); ostr << class_type << " \"" << c.full_name();
ostr << "\" as " << c.alias() << '\n'; ostr << "\" as " << c.alias() << '\n';
@@ -40,13 +45,18 @@ void generator::generate_alias(const class_ &c, std::ostream &ostr) const
void generator::generate_alias(const enum_ &e, std::ostream &ostr) const void generator::generate_alias(const enum_ &e, std::ostream &ostr) const
{ {
if (m_config.generate_packages())
ostr << "enum"
<< " \"" << e.name();
else
ostr << "enum" ostr << "enum"
<< " \"" << e.full_name(); << " \"" << e.full_name();
ostr << "\" as " << e.alias() << '\n'; ostr << "\" as " << e.alias() << '\n';
} }
void generator::generate(const class_ &c, std::ostream &ostr) const void generator::generate(
const class_ &c, std::ostream &ostr, std::ostream &relationships_ostr) const
{ {
namespace plantuml_common = clanguml::common::generators::plantuml; namespace plantuml_common = clanguml::common::generators::plantuml;
@@ -89,7 +99,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
return mp.to_string(m_config.using_namespace()); return mp.to_string(m_config.using_namespace());
}); });
auto args_string = fmt::format("{}", fmt::join(params, ", ")); auto args_string = fmt::format("{}", fmt::join(params, ", "));
if (m_config.generate_method_arguments() != if (m_config.generate_method_arguments() ==
config::method_arguments::abbreviated) { config::method_arguments::abbreviated) {
args_string = clanguml::util::abbreviate(args_string, 10); args_string = clanguml::util::abbreviate(args_string, 10);
} }
@@ -213,10 +223,11 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
generate_notes(ostr, c); generate_notes(ostr, c);
// Print relationships // Print relationships
ostr << all_relations_str.str(); relationships_ostr << all_relations_str.str();
} }
void generator::generate(const enum_ &e, std::ostream &ostr) const void generator::generate(
const enum_ &e, std::ostream &ostr, std::ostream &relationships_ostr) const
{ {
ostr << "enum " << e.alias(); ostr << "enum " << e.alias();
@@ -255,7 +266,7 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const
relstr << '\n'; relstr << '\n';
ostr << relstr.str(); relationships_ostr << relstr.str();
} }
catch (error::uml_alias_missing &ex) { catch (error::uml_alias_missing &ex) {
LOG_ERROR("Skipping {} relation from {} to {} due " LOG_ERROR("Skipping {} relation from {} to {} due "
@@ -269,40 +280,72 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const
generate_notes(ostr, e); generate_notes(ostr, e);
} }
void generator::generate(const package &p, std::ostream &ostr,
std::ostream &relationships_ostr) const
{
if (m_config.generate_packages()) {
LOG_DBG("Generating package {}", p.name());
ostr << "package [" << p.name() << "] ";
ostr << "as " << p.alias();
if (p.is_deprecated())
ostr << " <<deprecated>>";
if (!p.style().empty())
ostr << " " << p.style();
ostr << " {" << '\n';
}
for (const auto &subpackage : p) {
if (dynamic_cast<package *>(subpackage.get())) {
generate(
dynamic_cast<package &>(*subpackage), ostr, relationships_ostr);
}
if (dynamic_cast<class_ *>(subpackage.get())) {
generate_alias(dynamic_cast<class_ &>(*subpackage), ostr);
generate(
dynamic_cast<class_ &>(*subpackage), ostr, relationships_ostr);
}
if (dynamic_cast<enum_ *>(subpackage.get())) {
generate_alias(dynamic_cast<enum_ &>(*subpackage), ostr);
generate(
dynamic_cast<enum_ &>(*subpackage), ostr, relationships_ostr);
}
}
if (m_config.generate_packages()) {
ostr << "}" << '\n';
}
generate_notes(ostr, p);
}
void generator::generate(std::ostream &ostr) const void generator::generate(std::ostream &ostr) const
{ {
ostr << "@startuml" << '\n'; ostr << "@startuml" << '\n';
std::stringstream relationship_ostr;
generate_plantuml_directives(ostr, m_config.puml().before); generate_plantuml_directives(ostr, m_config.puml().before);
if (m_config.should_include_entities("classes")) { for (const auto &p : m_model) {
for (const auto &c : m_model.classes()) { if (dynamic_cast<package *>(p.get())) {
if (!m_config.should_include(c.name())) generate(dynamic_cast<package &>(*p), ostr, relationship_ostr);
continue; }
generate_alias(c, ostr); if (dynamic_cast<class_ *>(p.get())) {
generate_alias(dynamic_cast<class_ &>(*p), ostr);
generate(dynamic_cast<class_ &>(*p), ostr, relationship_ostr);
}
if (dynamic_cast<enum_ *>(p.get())) {
generate_alias(dynamic_cast<enum_ &>(*p), ostr);
generate(dynamic_cast<enum_ &>(*p), ostr, relationship_ostr);
}
ostr << '\n'; ostr << '\n';
} }
for (const auto &e : m_model.enums()) { ostr << relationship_ostr.str();
if (!m_config.should_include(e.name()))
continue;
generate_alias(e, ostr);
ostr << '\n';
}
for (const auto &c : m_model.classes()) {
if (!m_config.should_include(c.name()))
continue;
generate(c, ostr);
ostr << '\n';
}
}
if (m_config.should_include_entities("enums"))
for (const auto &e : m_model.enums()) {
generate(e, ostr);
ostr << '\n';
}
generate_config_layout_hints(ostr); generate_config_layout_hints(ostr);
@@ -310,5 +353,4 @@ void generator::generate(std::ostream &ostr) const
ostr << "@enduml" << '\n'; ostr << "@enduml" << '\n';
} }
} }

View File

@@ -49,6 +49,7 @@ using common_generator =
using clanguml::class_diagram::model::class_; using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::enum_; using clanguml::class_diagram::model::enum_;
using clanguml::common::model::package;
using clanguml::common::model::relationship_t; using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t; using clanguml::common::model::scope_t;
@@ -62,9 +63,14 @@ public:
void generate_alias(const enum_ &e, std::ostream &ostr) const; void generate_alias(const enum_ &e, std::ostream &ostr) const;
void generate(const class_ &c, std::ostream &ostr) const; void generate(const class_ &c, std::ostream &ostr,
std::ostream &relationships_ostr) const;
void generate(const enum_ &e, std::ostream &ostr) const; void generate(const enum_ &e, std::ostream &ostr,
std::ostream &relationships_ostr) const;
void generate(const package &p, std::ostream &ostr,
std::ostream &relationships_ostr) const;
void generate(std::ostream &ostr) const override; void generate(std::ostream &ostr) const override;
}; };

View File

@@ -96,16 +96,36 @@ void class_::add_type_alias(type_alias &&ta)
type_aliases_[ta.alias()] = std::move(ta); type_aliases_[ta.alias()] = std::move(ta);
} }
std::string class_::full_name_no_ns() const
{
using namespace clanguml::util;
std::ostringstream ostr;
ostr << name();
render_template_params(ostr);
return ostr.str();
}
std::string class_::full_name(bool relative) const std::string class_::full_name(bool relative) const
{ {
using namespace clanguml::util; using namespace clanguml::util;
std::ostringstream ostr; std::ostringstream ostr;
if (relative) if (relative && starts_with(get_namespace(), using_namespaces()))
ostr << ns_relative(using_namespaces(), name()); ostr << ns_relative(using_namespaces(), name_and_ns());
else else
ostr << name(); ostr << name_and_ns();
render_template_params(ostr);
return ostr.str();
}
std::ostringstream &class_::render_template_params(
std::ostringstream &ostr) const
{
if (!templates_.empty()) { if (!templates_.empty()) {
std::vector<std::string> tnames; std::vector<std::string> tnames;
std::transform(templates_.cbegin(), templates_.cend(), std::transform(templates_.cbegin(), templates_.cend(),
@@ -114,11 +134,11 @@ std::string class_::full_name(bool relative) const
if (!tmplt.type().empty()) if (!tmplt.type().empty())
res.push_back( res.push_back(
ns_relative(using_namespaces(), tmplt.type())); util::ns_relative(using_namespaces(), tmplt.type()));
if (!tmplt.name().empty()) if (!tmplt.name().empty())
res.push_back( res.push_back(
ns_relative(using_namespaces(), tmplt.name())); util::ns_relative(using_namespaces(), tmplt.name()));
if (!tmplt.default_value().empty()) { if (!tmplt.default_value().empty()) {
res.push_back("="); res.push_back("=");
@@ -129,8 +149,7 @@ std::string class_::full_name(bool relative) const
}); });
ostr << fmt::format("<{}>", fmt::join(tnames, ",")); ostr << fmt::format("<{}>", fmt::join(tnames, ","));
} }
return ostr;
return ostr.str();
} }
bool class_::is_abstract() const bool class_::is_abstract() const

View File

@@ -36,6 +36,11 @@ class class_ : public common::model::element,
public: public:
class_(const std::vector<std::string> &using_namespaces); class_(const std::vector<std::string> &using_namespaces);
class_(const class_ &) = delete;
class_(class_ &&) noexcept = delete;
class_ &operator=(const class_ &) = delete;
class_ &operator=(class_ &&) = delete;
bool is_struct() const; bool is_struct() const;
void is_struct(bool is_struct); void is_struct(bool is_struct);
@@ -64,9 +69,13 @@ public:
std::string full_name(bool relative = true) const override; std::string full_name(bool relative = true) const override;
std::string full_name_no_ns() const;
bool is_abstract() const; bool is_abstract() const;
private: private:
std::ostringstream &render_template_params(std::ostringstream &ostr) const;
bool is_struct_{false}; bool is_struct_{false};
bool is_template_{false}; bool is_template_{false};
bool is_template_instantiation_{false}; bool is_template_instantiation_{false};

View File

@@ -21,42 +21,89 @@
#include "util/error.h" #include "util/error.h"
#include "util/util.h" #include "util/util.h"
#include <cassert>
#include <iostream>
namespace clanguml::class_diagram::model { namespace clanguml::class_diagram::model {
const std::vector<class_> diagram::classes() const { return classes_; } const std::vector<type_safe::object_ref<const class_>> diagram::classes() const
{
return classes_;
}
const std::vector<enum_> diagram::enums() const { return enums_; } const std::vector<type_safe::object_ref<const enum_>> diagram::enums() const
{
return enums_;
}
bool diagram::has_class(const class_ &c) const bool diagram::has_class(const class_ &c) const
{ {
return std::any_of(classes_.cbegin(), classes_.cend(), return std::any_of(classes_.cbegin(), classes_.cend(),
[&c](const auto &cc) { return cc.full_name() == c.full_name(); }); [&c](const auto &cc) { return cc.get().full_name() == c.full_name(); });
} }
void diagram::add_type_alias(type_alias &&ta) bool diagram::has_enum(const enum_ &e) const
{ {
LOG_DBG("Adding global alias: {} -> {}", ta.alias(), ta.underlying_type()); return std::any_of(enums_.cbegin(), enums_.cend(),
[&e](const auto &ee) { return ee.get().full_name() == e.full_name(); });
type_aliases_[ta.alias()] = std::move(ta);
} }
void diagram::add_class(class_ &&c) void diagram::add_type_alias(std::unique_ptr<type_alias> &&ta)
{ {
LOG_DBG("Adding class: {}, {}", c.name(), c.full_name()); LOG_DBG(
if (!has_class(c)) "Adding global alias: {} -> {}", ta->alias(), ta->underlying_type());
classes_.emplace_back(std::move(c));
type_aliases_[ta->alias()] = std::move(ta);
}
void diagram::add_package(std::unique_ptr<common::model::package> &&p)
{
LOG_DBG("Adding namespace package: {}, {}", p->name(), p->full_name(true));
auto ns = p->get_relative_namespace();
add_element(ns, std::move(p));
}
void diagram::add_class(std::unique_ptr<class_> &&c)
{
LOG_DBG("Adding class: {}, {}", c->name(), c->full_name());
if (util::contains(c->name(), "::"))
throw std::runtime_error("Name cannot contain namespace: " + c->name());
if (util::contains(c->name(), "<"))
throw std::runtime_error("Name cannot contain <: " + c->name());
if (util::contains(c->name(), "*"))
throw std::runtime_error("Name cannot contain *: " + c->name());
if (!has_class(*c)) {
classes_.emplace_back(*c);
auto ns = c->get_relative_namespace();
auto name = c->name();
add_element(ns, std::move(c));
ns.push_back(name);
const auto ccc = get_element<class_>(ns);
assert(ccc.value().name() == name);
}
else else
LOG_DBG("Class {} ({}) already in the model", c.name(), c.full_name()); LOG_DBG(
"Class {} ({}) already in the model", c->name(), c->full_name());
} }
void diagram::add_enum(enum_ &&e) void diagram::add_enum(std::unique_ptr<enum_> &&e)
{ {
LOG_DBG("Adding enum: {}", e.name()); LOG_DBG("Adding enum: {}", e->name());
auto it = std::find(enums_.begin(), enums_.end(), e);
if (it == enums_.end()) assert(!util::contains(e->name(), "::"));
enums_.emplace_back(std::move(e));
if (!has_enum(*e)) {
enums_.emplace_back(*e);
auto ns = e->get_relative_namespace();
add_element(ns, std::move(e));
}
else else
LOG_DBG("Enum {} already in the model", e.name()); LOG_DBG("Enum {} already in the model", e->name());
} }
std::string diagram::to_alias(const std::string &full_name) const std::string diagram::to_alias(const std::string &full_name) const
@@ -64,18 +111,20 @@ std::string diagram::to_alias(const std::string &full_name) const
LOG_DBG("Looking for alias for {}", full_name); LOG_DBG("Looking for alias for {}", full_name);
for (const auto &c : classes_) { for (const auto &c : classes_) {
if (c.full_name() == full_name) { const auto &cc = c.get();
return c.alias(); if (cc.full_name() == full_name) {
return c->alias();
} }
} }
for (const auto &e : enums_) { for (const auto &e : enums_) {
if (e.full_name() == full_name) { if (e.get().full_name() == full_name) {
return e.alias(); return e->alias();
} }
} }
throw error::uml_alias_missing( throw error::uml_alias_missing(
fmt::format("Missing alias for {}", full_name)); fmt::format("Missing alias for {}", full_name));
} }
} }

View File

@@ -19,6 +19,8 @@
#include "class.h" #include "class.h"
#include "common/model/diagram.h" #include "common/model/diagram.h"
#include "common/model/nested_trait.h"
#include "common/model/package.h"
#include "enum.h" #include "enum.h"
#include "type_alias.h" #include "type_alias.h"
@@ -27,7 +29,9 @@
namespace clanguml::class_diagram::model { namespace clanguml::class_diagram::model {
class diagram : public clanguml::common::model::diagram { class diagram : public clanguml::common::model::diagram,
public clanguml::common::model::nested_trait<
clanguml::common::model::element> {
public: public:
diagram() = default; diagram() = default;
@@ -36,23 +40,29 @@ public:
diagram &operator=(const diagram &) = delete; diagram &operator=(const diagram &) = delete;
diagram &operator=(diagram &&) = default; diagram &operator=(diagram &&) = default;
const std::vector<class_> classes() const; const std::vector<type_safe::object_ref<const class_>> classes() const;
const std::vector<enum_> enums() const; const std::vector<type_safe::object_ref<const enum_>> enums() const;
bool has_class(const class_ &c) const; bool has_class(const class_ &c) const;
void add_type_alias(type_alias &&ta); bool has_enum(const enum_ &e) const;
void add_class(class_ &&c); void add_type_alias(std::unique_ptr<type_alias> &&ta);
void add_enum(enum_ &&e); void add_class(std::unique_ptr<class_> &&c);
void add_enum(std::unique_ptr<enum_> &&e);
void add_package(std::unique_ptr<common::model::package> &&p);
std::string to_alias(const std::string &full_name) const; std::string to_alias(const std::string &full_name) const;
friend void print_diagram_tree(const diagram &d, const int level);
private: private:
std::vector<class_> classes_; std::vector<type_safe::object_ref<const class_, false>> classes_;
std::vector<enum_> enums_; std::vector<type_safe::object_ref<const enum_, false>> enums_;
std::map<std::string, type_alias> type_aliases_; std::map<std::string, std::unique_ptr<type_alias>> type_aliases_;
}; };
} }

View File

@@ -29,7 +29,10 @@ enum_::enum_(const std::vector<std::string> &using_namespaces)
{ {
} }
bool operator==(const enum_ &l, const enum_ &r) { return l.name() == r.name(); } bool operator==(const enum_ &l, const enum_ &r)
{
return (l.get_namespace() == r.get_namespace()) && (l.name() == r.name());
}
std::string enum_::full_name(bool relative) const std::string enum_::full_name(bool relative) const
{ {

View File

@@ -29,6 +29,12 @@ class enum_ : public common::model::element,
public: public:
enum_(const std::vector<std::string> &using_namespaces); enum_(const std::vector<std::string> &using_namespaces);
enum_(const enum_ &) = delete;
enum_(enum_ &&) = default;
enum_ &operator=(const enum_ &) = delete;
enum_ &operator=(enum_ &&) = default;
// TODO: Do we need this?
friend bool operator==(const enum_ &l, const enum_ &r); friend bool operator==(const enum_ &l, const enum_ &r);
std::string full_name(bool relative = true) const override; std::string full_name(bool relative = true) const override;

View File

@@ -32,6 +32,53 @@ translation_unit_context::translation_unit_context(
{ {
} }
bool translation_unit_context::has_namespace_alias(
const std::string &full_name) const
{
bool res =
namespace_alias_index_.find(full_name) != namespace_alias_index_.end();
LOG_DBG("Alias {} {} found in index", full_name, res ? "" : "not");
return res;
}
void translation_unit_context::add_namespace_alias(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_namespace> ref)
{
if (!has_namespace_alias(full_name)) {
LOG_DBG(
"Stored namespace alias: {} -> {} ", full_name, ref.get().name());
namespace_alias_index_.emplace(full_name, std::move(ref));
}
}
type_safe::object_ref<const cppast::cpp_namespace>
translation_unit_context::get_namespace_alias(
const std::string &full_name) const
{
assert(has_namespace_alias(full_name));
return namespace_alias_index_.at(full_name);
}
type_safe::object_ref<const cppast::cpp_namespace>
translation_unit_context::get_namespace_alias_final(
const cppast::cpp_namespace &ns) const
{
auto ns_full_name = cx::util::full_name({}, ns);
ns_full_name = cx::util::ns(ns) + "::" + ns_full_name;
if (has_namespace_alias(ns_full_name)) {
return get_namespace_alias_final(
namespace_alias_index_.at(ns_full_name).get());
}
return type_safe::ref(ns);
}
bool translation_unit_context::has_type_alias( bool translation_unit_context::has_type_alias(
const std::string &full_name) const const std::string &full_name) const
{ {
@@ -132,4 +179,16 @@ clanguml::class_diagram::model::diagram &translation_unit_context::diagram()
return diagram_; return diagram_;
} }
void translation_unit_context::set_current_package(
type_safe::optional_ref<common::model::package> p)
{
current_package_ = p;
}
type_safe::optional_ref<common::model::package>
translation_unit_context::get_current_package() const
{
return current_package_;
}
} }

View File

@@ -20,6 +20,7 @@
#include "config/config.h" #include "config/config.h"
#include <cppast/cpp_entity_index.hpp> #include <cppast/cpp_entity_index.hpp>
#include <cppast/cpp_namespace.hpp>
#include <cppast/cpp_type.hpp> #include <cppast/cpp_type.hpp>
#include <type_safe/reference.hpp> #include <type_safe/reference.hpp>
@@ -31,6 +32,17 @@ public:
clanguml::class_diagram::model::diagram &diagram, clanguml::class_diagram::model::diagram &diagram,
const clanguml::config::class_diagram &config); const clanguml::config::class_diagram &config);
bool has_namespace_alias(const std::string &full_name) const;
void add_namespace_alias(const std::string &full_name,
type_safe::object_ref<const cppast::cpp_namespace> ref);
type_safe::object_ref<const cppast::cpp_namespace> get_namespace_alias(
const std::string &full_name) const;
type_safe::object_ref<const cppast::cpp_namespace>
get_namespace_alias_final(const cppast::cpp_namespace &t) const;
bool has_type_alias(const std::string &full_name) const; bool has_type_alias(const std::string &full_name) const;
void add_type_alias(const std::string &full_name, void add_type_alias(const std::string &full_name,
@@ -62,6 +74,10 @@ public:
clanguml::class_diagram::model::diagram &diagram(); clanguml::class_diagram::model::diagram &diagram();
void set_current_package(type_safe::optional_ref<common::model::package> p);
type_safe::optional_ref<common::model::package> get_current_package() const;
private: private:
// Current visitor namespace // Current visitor namespace
std::vector<std::string> namespace_; std::vector<std::string> namespace_;
@@ -75,6 +91,10 @@ private:
// Reference to class diagram config // Reference to class diagram config
const clanguml::config::class_diagram &config_; const clanguml::config::class_diagram &config_;
// Map of discovered aliases (declared with 'namespace' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_namespace>>
namespace_alias_index_;
// Map of discovered aliases (declared with 'using' keyword) // Map of discovered aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>> std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_index_; alias_index_;
@@ -82,6 +102,8 @@ private:
// Map of discovered template aliases (declared with 'using' keyword) // Map of discovered template aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>> std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_template_index_; alias_template_index_;
type_safe::optional_ref<common::model::package> current_package_;
}; };
} }

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,11 @@
#include <cppast/visitor.hpp> #include <cppast/visitor.hpp>
#include <type_safe/reference.hpp> #include <type_safe/reference.hpp>
#include <class_diagram/model/class.h>
#include <common/model/enums.h>
#include <cppast/cpp_alias_template.hpp>
#include <cppast/cpp_type_alias.hpp>
#include <deque>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
@@ -41,6 +46,9 @@
namespace clanguml::class_diagram::visitor { namespace clanguml::class_diagram::visitor {
using found_relationships_t =
std::vector<std::pair<std::string, common::model::relationship_t>>;
class translation_unit_visitor { class translation_unit_visitor {
public: public:
translation_unit_visitor(cppast::cpp_entity_index &idx, translation_unit_visitor(cppast::cpp_entity_index &idx,
@@ -99,8 +107,7 @@ public:
const std::set<std::string> &template_parameter_names = {}); const std::set<std::string> &template_parameter_names = {});
bool find_relationships(const cppast::cpp_type &t, bool find_relationships(const cppast::cpp_type &t,
std::vector<std::pair<std::string, found_relationships_t &relationships,
clanguml::common::model::relationship_t>> &relationships,
clanguml::common::model::relationship_t relationship_hint = clanguml::common::model::relationship_t relationship_hint =
clanguml::common::model::relationship_t::kNone); clanguml::common::model::relationship_t::kNone);
@@ -119,8 +126,42 @@ public:
void process_friend(const cppast::cpp_friend &t, void process_friend(const cppast::cpp_friend &t,
clanguml::class_diagram::model::class_ &parent); clanguml::class_diagram::model::class_ &parent);
void process_namespace(const cppast::cpp_entity &e,
const cppast::cpp_namespace &ns_declaration);
void process_type_alias(const cppast::cpp_type_alias &ta);
void process_type_alias_template(const cppast::cpp_alias_template &at);
void process_class_children(const cppast::cpp_class &cls, model::class_ &c);
void process_class_bases(
const cppast::cpp_class &cls, model::class_ &c) const;
void process_unexposed_template_specialization_parameters(
const type_safe::optional_ref<const cppast::cpp_template_specialization>
&tspec,
model::class_ &c) const;
void process_exposed_template_specialization_parameters(
const type_safe::optional_ref<const cppast::cpp_template_specialization>
&tspec,
model::class_ &c);
void process_scope_template_parameters(
model::class_ &c, const cppast::cpp_scope_name &scope);
bool process_template_parameters(const cppast::cpp_class &cls,
model::class_ &c,
const type_safe::optional_ref<const cppast::cpp_template_specialization>
&tspec);
void process_class_containment(
const cppast::cpp_class &cls, model::class_ &c) const;
private: private:
clanguml::class_diagram::model::class_ build_template_instantiation( std::unique_ptr<clanguml::class_diagram::model::class_>
build_template_instantiation(
const cppast::cpp_template_instantiation_type &t, const cppast::cpp_template_instantiation_type &t,
std::optional<clanguml::class_diagram::model::class_ *> parent = {}); std::optional<clanguml::class_diagram::model::class_ *> parent = {});
@@ -130,6 +171,54 @@ private:
*/ */
const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t); const cppast::cpp_type &resolve_alias(const cppast::cpp_type &t);
const cppast::cpp_type &resolve_alias_template(
const cppast::cpp_type &type);
bool find_relationships_in_array(
found_relationships_t &relationships, const cppast::cpp_type &t);
bool find_relationships_in_pointer(const cppast::cpp_type &t_,
found_relationships_t &relationships,
const common::model::relationship_t &relationship_hint);
bool find_relationships_in_reference(const cppast::cpp_type &t_,
found_relationships_t &relationships,
const common::model::relationship_t &relationship_hint);
bool find_relationships_in_user_defined_type(const cppast::cpp_type &t_,
found_relationships_t &relationships, const std::string &fn,
common::model::relationship_t &relationship_type,
const cppast::cpp_type &t);
bool find_relationships_in_template_instantiation(const cppast::cpp_type &t,
const std::string &fn, found_relationships_t &relationships,
common::model::relationship_t relationship_type);
void build_template_instantiation_primary_template(
const cppast::cpp_template_instantiation_type &t,
clanguml::class_diagram::model::class_ &tinst,
std::deque<std::tuple<std::string, int, bool>> &template_base_params,
std::optional<clanguml::class_diagram::model::class_ *> &parent,
std::string &full_template_name) const;
void build_template_instantiation_process_type_argument(
const std::optional<clanguml::class_diagram::model::class_ *> &parent,
model::class_ &tinst, const cppast::cpp_template_argument &targ,
model::class_template &ct);
void build_template_instantiation_process_expression_argument(
const cppast::cpp_template_argument &targ,
model::class_template &ct) const;
bool build_template_instantiation_add_base_classes(model::class_ &tinst,
std::deque<std::tuple<std::string, int, bool>> &template_base_params,
int arg_index, bool variadic_params,
const model::class_template &ct) const;
void process_function_parameter_find_relationships_in_template(
model::class_ &c, const std::set<std::string> &template_parameter_names,
const cppast::cpp_type &t);
// ctx allows to track current visitor context, e.g. current namespace // ctx allows to track current visitor context, e.g. current namespace
translation_unit_context ctx; translation_unit_context ctx;
}; };

View File

@@ -20,6 +20,8 @@
#include "util/util.h" #include "util/util.h"
#include <ostream>
namespace clanguml::common::model { namespace clanguml::common::model {
std::atomic_uint64_t element::m_nextId = 1; std::atomic_uint64_t element::m_nextId = 1;
@@ -28,6 +30,8 @@ element::element(const std::vector<std::string> &using_namespaces)
: using_namespaces_{using_namespaces} : using_namespaces_{using_namespaces}
, m_id{m_nextId++} , m_id{m_nextId++}
{ {
for (const auto &n : using_namespaces_)
assert(!util::contains(n, "::"));
} }
std::string element::alias() const { return fmt::format("C_{:010}", m_id); } std::string element::alias() const { return fmt::format("C_{:010}", m_id); }
@@ -41,6 +45,13 @@ void element::add_relationship(relationship &&cr)
return; return;
} }
if ((cr.type() == relationship_t::kInstantiation) &&
(cr.destination() == full_name(true))) {
LOG_WARN("Skipping self instantiation relationship for {}",
cr.destination());
return;
}
LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(), LOG_DBG("Adding relationship: '{}' - {} - '{}'", cr.destination(),
to_string(cr.type()), full_name(true)); to_string(cr.type()), full_name(true));
@@ -50,6 +61,9 @@ void element::add_relationship(relationship &&cr)
void element::set_using_namespaces(const std::vector<std::string> &un) void element::set_using_namespaces(const std::vector<std::string> &un)
{ {
for (const auto &n : un)
assert(!util::contains(n, "::"));
using_namespaces_ = un; using_namespaces_ = un;
} }
@@ -66,4 +80,19 @@ const std::vector<relationship> &element::relationships() const
} }
void element::append(const element &e) { decorated_element::append(e); } void element::append(const element &e) { decorated_element::append(e); }
bool operator==(const element &l, const element &r)
{
return l.full_name(false) == r.full_name(false);
}
std::ostream &operator<<(std::ostream &out, const element &rhs)
{
out << "(" << rhs.name() << ", ns=["
<< util::join(rhs.get_namespace(), "::") << "], full_name=["
<< rhs.full_name(true) << "])";
return out;
}
} }

View File

@@ -19,8 +19,10 @@
#include "decorated_element.h" #include "decorated_element.h"
#include "relationship.h" #include "relationship.h"
#include "util/util.h"
#include <atomic> #include <atomic>
#include <exception>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -30,16 +32,32 @@ class element : public decorated_element {
public: public:
element(const std::vector<std::string> &using_namespaces); element(const std::vector<std::string> &using_namespaces);
virtual ~element() = default;
std::string alias() const; std::string alias() const;
void set_name(const std::string &name) { name_ = name; } void set_name(const std::string &name) { name_ = name; }
std::string name() const { return name_; } std::string name() const { return name_; }
std::string name_and_ns() const
{
auto ns = namespace_;
ns.push_back(name());
return util::join(ns, "::");
}
void set_namespace(const std::vector<std::string> &ns) { namespace_ = ns; } void set_namespace(const std::vector<std::string> &ns) { namespace_ = ns; }
std::vector<std::string> get_namespace() const { return namespace_; } std::vector<std::string> get_namespace() const { return namespace_; }
std::vector<std::string> get_relative_namespace() const
{
auto relative_ns = namespace_;
util::remove_prefix(relative_ns, using_namespaces_);
return relative_ns;
}
virtual std::string full_name(bool relative) const { return name(); } virtual std::string full_name(bool relative) const { return name(); }
void set_using_namespaces(const std::vector<std::string> &un); void set_using_namespaces(const std::vector<std::string> &un);
@@ -54,6 +72,10 @@ public:
void append(const element &e); void append(const element &e);
friend bool operator==(const element &l, const element &r);
friend std::ostream &operator<<(std::ostream &out, const element &rhs);
protected: protected:
const uint64_t m_id{0}; const uint64_t m_id{0};

View File

@@ -21,6 +21,7 @@
#include <type_safe/optional_ref.hpp> #include <type_safe/optional_ref.hpp>
#include <iostream>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -37,7 +38,7 @@ public:
virtual ~nested_trait() = default; virtual ~nested_trait() = default;
void add_element(std::unique_ptr<T> p) template <typename V = T> void add_element(std::unique_ptr<V> p)
{ {
auto it = std::find_if(elements_.begin(), elements_.end(), auto it = std::find_if(elements_.begin(), elements_.end(),
[&p](const auto &e) { return *e == *p; }); [&p](const auto &e) { return *e == *p; });
@@ -50,11 +51,12 @@ public:
} }
} }
void add_element(std::vector<std::string> path, std::unique_ptr<T> p) template <typename V = T>
void add_element(std::vector<std::string> path, std::unique_ptr<V> p)
{ {
assert(p); assert(p);
LOG_DBG("Adding nested element {} at path '{}'", p->name(), LOG_DBG("Adding nested element {} at path {}", p->name(),
fmt::join(path, "::")); fmt::join(path, "::"));
if (path.empty()) { if (path.empty()) {
@@ -64,42 +66,57 @@ public:
auto parent = get_element(path); auto parent = get_element(path);
if (parent) if (parent && dynamic_cast<nested_trait<T> *>(&parent.value()))
parent.value().add_element(std::move(p)); dynamic_cast<nested_trait<T> &>(parent.value())
else .template add_element<V>(std::move(p));
else {
spdlog::error( spdlog::error(
"No parent element found at: {}", fmt::join(path, "::")); "No parent element found at: {}", fmt::join(path, "::"));
throw std::runtime_error("No parent element found");
}
} }
type_safe::optional_ref<T> get_element(std::vector<std::string> path) const template <typename V = T>
auto get_element(std::vector<std::string> path) const
{ {
LOG_DBG("Getting nested element at path: {}", fmt::join(path, "::")); LOG_DBG("Getting nested element at path: {}", fmt::join(path, "::"));
if (path.empty() || !has_element(path.at(0))) { if (path.empty() || !has_element(path.at(0))) {
LOG_WARN("Nested element {} not found in element", LOG_WARN("Nested element {} not found in element",
fmt::join(path, "::")); fmt::join(path, "::"));
return {}; return type_safe::optional_ref<V>{};
} }
auto p = get_element(path.at(0));
if (path.size() == 1) if (path.size() == 1)
return p; return get_element<V>(path.at(0));
return p.value().get_element( auto p = get_element<T>(path.at(0));
if (!p)
return type_safe::optional_ref<V>{};
if (dynamic_cast<nested_trait<T> *>(&p.value()))
return dynamic_cast<nested_trait<T> &>(p.value()).get_element<V>(
std::vector<std::string>(path.begin() + 1, path.end())); std::vector<std::string>(path.begin() + 1, path.end()));
return type_safe::optional_ref<V>{};
} }
type_safe::optional_ref<T> get_element(const std::string &name) const template <typename V = T> auto get_element(const std::string &name) const
{ {
auto it = std::find_if(elements_.cbegin(), elements_.cend(), auto it = std::find_if(elements_.cbegin(), elements_.cend(),
[&](const auto &p) { return name == p->name(); }); [&](const auto &p) { return name == p->name(); });
if (it == elements_.end()) if (it == elements_.end())
return {}; return type_safe::optional_ref<V>{type_safe::nullopt};
assert(it->get() != nullptr); assert(it->get() != nullptr);
return type_safe::ref(*(it->get())); if (dynamic_cast<V *>(it->get()))
return type_safe::optional_ref<V>{
type_safe::ref<V>(dynamic_cast<V &>(*it->get()))};
return type_safe::optional_ref<V>{type_safe::nullopt};
} }
bool has_element(const std::string &name) const bool has_element(const std::string &name) const
@@ -118,6 +135,24 @@ public:
auto begin() const { return elements_.begin(); } auto begin() const { return elements_.begin(); }
auto end() const { return elements_.end(); } auto end() const { return elements_.end(); }
void print_tree(const int level)
{
const auto &d = *this;
if (level == 0) {
std::cout << "--- Printing tree:\n";
}
for (const auto &e : d) {
if (dynamic_cast<nested_trait<T> *>(e.get())) {
std::cout << std::string(level, ' ') << "[" << *e << "]\n";
dynamic_cast<nested_trait<T> *>(e.get())->print_tree(level + 1);
}
else {
std::cout << std::string(level, ' ') << "- " << *e << "]\n";
}
}
}
private: private:
std::vector<std::unique_ptr<T>> elements_; std::vector<std::unique_ptr<T>> elements_;
}; };

View File

@@ -1,5 +1,5 @@
/** /**
* src/package_diagram/model/class.h * src/common/model/class.h
* *
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com> * Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
* *
@@ -22,7 +22,7 @@
#include <sstream> #include <sstream>
namespace clanguml::package_diagram::model { namespace clanguml::common::model {
package::package(const std::vector<std::string> &using_namespaces) package::package(const std::vector<std::string> &using_namespaces)
: element{using_namespaces} : element{using_namespaces}
{ {
@@ -43,11 +43,6 @@ std::string package::full_name(bool relative) const
return fmt::format("{}", fmt::join(fn, "::")); return fmt::format("{}", fmt::join(fn, "::"));
} }
bool operator==(const package &l, const package &r)
{
return l.full_name(false) == r.full_name(false);
}
bool package::is_deprecated() const { return is_deprecated_; } bool package::is_deprecated() const { return is_deprecated_; }
void package::set_deprecated(bool deprecated) { is_deprecated_ = deprecated; } void package::set_deprecated(bool deprecated) { is_deprecated_ = deprecated; }

View File

@@ -29,11 +29,11 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace clanguml::package_diagram::model { namespace clanguml::common::model {
class package : public common::model::element, class package : public element,
public common::model::stylable_element, public stylable_element,
public common::model::nested_trait<package> { public nested_trait<element> {
public: public:
package(const std::vector<std::string> &using_namespaces); package(const std::vector<std::string> &using_namespaces);
@@ -44,8 +44,6 @@ public:
std::string full_name(bool relative) const override; std::string full_name(bool relative) const override;
friend bool operator==(const package &l, const package &r);
bool is_deprecated() const; bool is_deprecated() const;
void set_deprecated(bool deprecated); void set_deprecated(bool deprecated);

View File

@@ -129,6 +129,20 @@ bool diagram::should_include_relationship(const std::string &rel)
return false; return false;
} }
bool diagram::should_include(
const std::pair<std::vector<std::string>, std::string> &name) const
{
return should_include(std::get<0>(name), std::get<1>(name));
}
bool diagram::should_include(
const std::vector<std::string> &ns, const std::string &name) const
{
auto ns_and_name = ns;
ns_and_name.push_back(name);
return should_include(util::join(ns_and_name, "::"));
}
bool diagram::should_include(const std::string &name_) const bool diagram::should_include(const std::string &name_) const
{ {
auto name = clanguml::util::unqualify(name_); auto name = clanguml::util::unqualify(name_);
@@ -150,7 +164,7 @@ bool diagram::should_include(const std::string &name_) const
return true; return true;
} }
spdlog::debug("Skipping from diagram: {}", name); LOG_DBG("Skipping from diagram: {}", name);
return false; return false;
} }
@@ -396,6 +410,7 @@ template <> struct convert<class_diagram> {
get_option(node, rhs.layout); get_option(node, rhs.layout);
get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.include_relations_also_as_members);
get_option(node, rhs.generate_method_arguments); get_option(node, rhs.generate_method_arguments);
get_option(node, rhs.generate_packages);
return true; return true;
} }
@@ -476,6 +491,7 @@ template <> struct convert<config> {
get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.include_relations_also_as_members);
get_option(node, rhs.puml); get_option(node, rhs.puml);
get_option(node, rhs.generate_method_arguments); get_option(node, rhs.generate_method_arguments);
get_option(node, rhs.generate_packages);
auto diagrams = node["diagrams"]; auto diagrams = node["diagrams"];

View File

@@ -87,6 +87,7 @@ struct inheritable_diagram_options {
option<plantuml> puml{"plantuml", option_inherit_mode::append}; option<plantuml> puml{"plantuml", option_inherit_mode::append};
option<method_arguments> generate_method_arguments{ option<method_arguments> generate_method_arguments{
"generate_method_arguments", method_arguments::full}; "generate_method_arguments", method_arguments::full};
option<bool> generate_packages{"generate_packages", false};
void inherit(const inheritable_diagram_options &parent); void inherit(const inheritable_diagram_options &parent);
}; };
@@ -102,9 +103,16 @@ struct diagram : public inheritable_diagram_options {
bool should_include_relationship(const std::string &rel); bool should_include_relationship(const std::string &rel);
bool should_include(const std::string &name_) const; bool should_include(
const std::pair<std::vector<std::string>, std::string> &name) const;
bool should_include(
const std::vector<std::string> &ns, const std::string &name) const;
bool should_include(const common::model::scope_t scope) const; bool should_include(const common::model::scope_t scope) const;
bool should_include(const std::string &name_) const;
private:
}; };
struct source_location { struct source_location {

View File

@@ -56,6 +56,8 @@ template <typename T> struct option {
} }
} }
void operator()(const T &v) { set(v); }
T &operator()() { return value; } T &operator()() { return value; }
const T &operator()() const { return value; } const T &operator()() const { return value; }

View File

@@ -126,6 +126,16 @@ bool is_inside_class(const cppast::cpp_entity &e)
return false; return false;
} }
std::pair<std::vector<std::string>, std::string> split_ns(
const std::string &full_name)
{
auto name_before_template = ::clanguml::util::split(full_name, "<")[0];
auto ns = ::clanguml::util::split(name_before_template, "::");
auto name = ns.back();
ns.pop_back();
return {ns, name};
}
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx) std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx)
{ {
if (t.kind() == cppast::cpp_type_kind::user_defined_t && if (t.kind() == cppast::cpp_type_kind::user_defined_t &&

View File

@@ -58,6 +58,9 @@ type_safe::optional_ref<const cppast::cpp_namespace> entity_ns(
std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx); std::string ns(const cppast::cpp_type &t, const cppast::cpp_entity_index &idx);
std::pair<std::vector<std::string>, std::string> split_ns(
const std::string &full_name);
bool is_inside_class(const cppast::cpp_entity &e); bool is_inside_class(const cppast::cpp_entity &e);
} // namespace util } // namespace util
} // namespace cx } // namespace cx

View File

@@ -54,8 +54,9 @@ void generator::generate_relationships(
} }
// Process it's subpackages relationships // Process it's subpackages relationships
for (const std::unique_ptr<package> &subpackage : p) { for (const auto &subpackage : p) {
generate_relationships(*subpackage, ostr); generate_relationships(
dynamic_cast<const package &>(*subpackage), ostr);
} }
} }
@@ -77,7 +78,7 @@ void generator::generate(const package &p, std::ostream &ostr) const
ostr << " {" << '\n'; ostr << " {" << '\n';
for (const auto &subpackage : p) { for (const auto &subpackage : p) {
generate(*subpackage, ostr); generate(dynamic_cast<const package &>(*subpackage), ostr);
} }
ostr << "}" << '\n'; ostr << "}" << '\n';
@@ -93,14 +94,14 @@ void generator::generate(std::ostream &ostr) const
if (m_config.should_include_entities("packages")) { if (m_config.should_include_entities("packages")) {
for (const auto &p : m_model) { for (const auto &p : m_model) {
generate(*p, ostr); generate(dynamic_cast<package &>(*p), ostr);
ostr << '\n'; ostr << '\n';
} }
} }
// Process package relationships // Process package relationships
for (const auto &p : m_model) { for (const auto &p : m_model) {
generate_relationships(*p, ostr); generate_relationships(dynamic_cast<package &>(*p), ostr);
ostr << '\n'; ostr << '\n';
} }

View File

@@ -18,11 +18,11 @@
#pragma once #pragma once
#include "common/generators/plantuml/generator.h" #include "common/generators/plantuml/generator.h"
#include "common/model/package.h"
#include "common/model/relationship.h" #include "common/model/relationship.h"
#include "config/config.h" #include "config/config.h"
#include "cx/compilation_database.h" #include "cx/compilation_database.h"
#include "package_diagram/model/diagram.h" #include "package_diagram/model/diagram.h"
#include "package_diagram/model/package.h"
#include "package_diagram/visitor/translation_unit_visitor.h" #include "package_diagram/visitor/translation_unit_visitor.h"
#include "util/util.h" #include "util/util.h"
@@ -46,9 +46,9 @@ template <typename C, typename D>
using common_generator = using common_generator =
clanguml::common::generators::plantuml::generator<C, D>; clanguml::common::generators::plantuml::generator<C, D>;
using clanguml::common::model::package;
using clanguml::common::model::relationship_t; using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t; using clanguml::common::model::scope_t;
using clanguml::package_diagram::model::package;
using namespace clanguml::util; using namespace clanguml::util;
class generator : public common_generator<diagram_config, diagram_model> { class generator : public common_generator<diagram_config, diagram_model> {

View File

@@ -33,7 +33,7 @@ std::string diagram::to_alias(const std::string &full_name) const
throw error::uml_alias_missing( throw error::uml_alias_missing(
fmt::format("Missing alias for '{}'", full_name)); fmt::format("Missing alias for '{}'", full_name));
auto package = get_element(fn); auto package = get_element<common::model::package>(fn);
if (!package) if (!package)
throw error::uml_alias_missing( throw error::uml_alias_missing(

View File

@@ -18,7 +18,7 @@
#pragma once #pragma once
#include "common/model/diagram.h" #include "common/model/diagram.h"
#include "package.h" #include "common/model/package.h"
#include <type_safe/optional_ref.hpp> #include <type_safe/optional_ref.hpp>
@@ -28,7 +28,8 @@
namespace clanguml::package_diagram::model { namespace clanguml::package_diagram::model {
class diagram : public clanguml::common::model::diagram, class diagram : public clanguml::common::model::diagram,
public clanguml::common::model::nested_trait<package> { public clanguml::common::model::nested_trait<
clanguml::common::model::element> {
public: public:
diagram() = default; diagram() = default;

View File

@@ -180,12 +180,12 @@ clanguml::package_diagram::model::diagram &translation_unit_context::diagram()
} }
void translation_unit_context::set_current_package( void translation_unit_context::set_current_package(
type_safe::optional_ref<model::package> p) type_safe::optional_ref<common::model::package> p)
{ {
current_package_ = p; current_package_ = p;
} }
type_safe::optional_ref<model::package> type_safe::optional_ref<common::model::package>
translation_unit_context::get_current_package() const translation_unit_context::get_current_package() const
{ {
return current_package_; return current_package_;

View File

@@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "common/model/package.h"
#include "config/config.h" #include "config/config.h"
#include "package_diagram/model/diagram.h" #include "package_diagram/model/diagram.h"
@@ -75,9 +76,9 @@ public:
clanguml::package_diagram::model::diagram &diagram(); clanguml::package_diagram::model::diagram &diagram();
void set_current_package(type_safe::optional_ref<model::package> p); void set_current_package(type_safe::optional_ref<common::model::package> p);
type_safe::optional_ref<model::package> get_current_package() const; type_safe::optional_ref<common::model::package> get_current_package() const;
private: private:
// Current visitor namespace // Current visitor namespace
@@ -104,7 +105,7 @@ private:
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>> std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_template_index_; alias_template_index_;
type_safe::optional_ref<model::package> current_package_; type_safe::optional_ref<common::model::package> current_package_;
}; };
} }

View File

@@ -37,11 +37,11 @@ namespace clanguml::package_diagram::visitor {
using clanguml::class_diagram::model::type_alias; using clanguml::class_diagram::model::type_alias;
using clanguml::common::model::access_t; using clanguml::common::model::access_t;
using clanguml::common::model::package;
using clanguml::common::model::relationship; using clanguml::common::model::relationship;
using clanguml::common::model::relationship_t; using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t; using clanguml::common::model::scope_t;
using clanguml::package_diagram::model::diagram; using clanguml::package_diagram::model::diagram;
using clanguml::package_diagram::model::package;
namespace detail { namespace detail {
scope_t cpp_access_specifier_to_scope( scope_t cpp_access_specifier_to_scope(
@@ -98,10 +98,8 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
ctx.config().using_namespace()[0], "::"); ctx.config().using_namespace()[0], "::");
if (!util::starts_with(usn, package_path)) { if (!util::starts_with(usn, package_path)) {
auto p = std::make_unique<package>( auto p = std::make_unique<package>(usn);
ctx.config().using_namespace());
util::remove_prefix(package_path, usn); util::remove_prefix(package_path, usn);
util::remove_prefix(package_parent, usn);
p->set_name(e.name()); p->set_name(e.name());
p->set_namespace(package_parent); p->set_namespace(package_parent);
@@ -122,10 +120,11 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
} }
if (!p->skip()) { if (!p->skip()) {
ctx.diagram().add_element( auto rns = p->get_relative_namespace();
package_parent, std::move(p)); ctx.diagram().add_element(rns, std::move(p));
ctx.set_current_package( ctx.set_current_package(
ctx.diagram().get_element(package_path)); ctx.diagram().get_element<package>(
package_path));
} }
} }
@@ -352,6 +351,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
const auto fn = cx::util::full_name( const auto fn = cx::util::full_name(
resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false); resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false);
auto t_ns = util::split(fn, "::"); auto t_ns = util::split(fn, "::");
auto t_name = t_ns.back();
t_ns.pop_back(); t_ns.pop_back();
const auto &t_raw = resolve_alias(cppast::remove_cv(t_)); const auto &t_raw = resolve_alias(cppast::remove_cv(t_));
@@ -464,7 +464,7 @@ bool translation_unit_visitor::find_relationships(const cppast::cpp_type &t_,
found = find_relationships(args[0u].type().value(), relationships, found = find_relationships(args[0u].type().value(), relationships,
relationship_t::kDependency); relationship_t::kDependency);
} }
else if (ctx.config().should_include(fn)) { else if (ctx.config().should_include(t_ns, t_name)) {
LOG_DBG("User defined template instantiation: {} | {}", LOG_DBG("User defined template instantiation: {} | {}",
cppast::to_string(t_), cppast::to_string(t_.canonical())); cppast::to_string(t_), cppast::to_string(t_.canonical()));

View File

@@ -35,10 +35,10 @@
#include <type_safe/reference.hpp> #include <type_safe/reference.hpp>
#include <common/model/enums.h> #include <common/model/enums.h>
#include <common/model/package.h>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <package_diagram/model/package.h>
#include <string> #include <string>
namespace clanguml::package_diagram::visitor { namespace clanguml::package_diagram::visitor {

View File

@@ -80,7 +80,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e)
.value(); .value();
m.from = cx::util::ns(caller) + "::" + caller.name(); m.from = cx::util::ns(caller) + "::" + caller.name();
if (!ctx.config().should_include(m.from)) if (!ctx.config().should_include(
util::split(cx::util::ns(caller), "::"), caller.name()))
continue; continue;
if (caller.kind() == cpp_entity_kind::function_t) if (caller.kind() == cpp_entity_kind::function_t)
@@ -96,7 +97,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e)
if (callee.kind() == cpp_entity_kind::function_t) if (callee.kind() == cpp_entity_kind::function_t)
m.to += "()"; m.to += "()";
if (!ctx.config().should_include(m.to)) if (!ctx.config().should_include(
util::split(cx::util::ns(callee), "::"), callee.name()))
continue; continue;
m.to_usr = type_safe::get(function_call.get_callee_method_id()); m.to_usr = type_safe::get(function_call.get_callee_method_id());

View File

@@ -44,23 +44,22 @@ std::vector<std::string> split(std::string str, std::string delimiter)
{ {
std::vector<std::string> result; std::vector<std::string> result;
if (!contains(str, delimiter))
result.push_back(str);
else
while (str.size()) { while (str.size()) {
int index = str.find(delimiter); int index = str.find(delimiter);
if (index != std::string::npos) { if (index != std::string::npos) {
result.push_back(str.substr(0, index)); result.push_back(str.substr(0, index));
str = str.substr(index + delimiter.size()); str = str.substr(index + delimiter.size());
if (str.size() == 0)
result.push_back(str);
} }
else { else {
if (!str.empty())
result.push_back(str); result.push_back(str);
str = ""; str = "";
} }
} }
if (result.empty())
result.push_back(str);
return result; return result;
} }

View File

@@ -22,6 +22,7 @@
#include <algorithm> #include <algorithm>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <type_traits>
#include <vector> #include <vector>
namespace clanguml { namespace clanguml {
@@ -190,6 +191,9 @@ bool contains(const T &container, const E &element)
[&element](const auto &e) { return *e == *element; }) != [&element](const auto &e) { return *e == *element; }) !=
container.end(); container.end();
} }
else if constexpr (std::is_same_v<std::remove_cv_t<T>, std::string>) {
return container.find(element) != std::string::npos;
}
else { else {
return std::find(container.begin(), container.end(), element) != return std::find(container.begin(), container.end(), element) !=
container.end(); container.end();

View File

@@ -30,8 +30,8 @@ TEST_CASE("t00002", "[test-case][class]")
REQUIRE(diagram->exclude().namespaces.size() == 0); REQUIRE(diagram->exclude().namespaces.size() == 0);
REQUIRE(diagram->should_include("clanguml::t00002::A")); REQUIRE(diagram->should_include({"clanguml", "t00002"}, "A"));
REQUIRE(!diagram->should_include("std::vector")); REQUIRE(!diagram->should_include({"std"}, "vector"));
auto model = generate_class_diagram(db, diagram); auto model = generate_class_diagram(db, diagram);

View File

@@ -39,12 +39,23 @@ TEST_CASE("t00014", "[test-case][class]")
REQUIRE_THAT(puml, IsClassTemplate("A", "T,std::string")); REQUIRE_THAT(puml, IsClassTemplate("A", "T,std::string"));
REQUIRE_THAT(puml, IsClassTemplate("A", "bool,std::string")); REQUIRE_THAT(puml, IsClassTemplate("A", "bool,std::string"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "float")); REQUIRE_THAT(puml, IsClassTemplate("AString", "float"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "int"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "std::string"));
REQUIRE_THAT( REQUIRE_THAT(
puml, !IsClassTemplate("std::std::function", "void(T...,int),int)")); puml, !IsClassTemplate("std::std::function", "void(T...,int),int)"));
REQUIRE_THAT(puml, IsInstantiation(_A("A<T,P>"), _A("A<T,std::string>"))); REQUIRE_THAT(puml, IsInstantiation(_A("A<T,P>"), _A("A<T,std::string>")));
REQUIRE_THAT( REQUIRE_THAT(
puml, IsInstantiation(_A("A<T,std::string>"), _A("AString<float>"))); puml, IsInstantiation(_A("A<T,std::string>"), _A("AString<float>")));
REQUIRE_THAT(
puml, IsInstantiation(_A("A<T,std::string>"), _A("AString<int>")));
REQUIRE_THAT(
puml, !IsInstantiation(_A("AString<int>"), _A("AString<int>")));
REQUIRE_THAT(puml,
IsInstantiation(_A("A<T,std::string>"), _A("AString<std::string>")));
REQUIRE_THAT(puml,
!IsInstantiation(
_A("AString<std::string>"), _A("AString<std::string>")));
REQUIRE_THAT( REQUIRE_THAT(
puml, IsAggregation(_A("R"), _A("A<bool,std::string>"), "-boolstring")); puml, IsAggregation(_A("R"), _A("A<bool,std::string>"), "-boolstring"));
REQUIRE_THAT( REQUIRE_THAT(

13
tests/t00036/.clang-uml Normal file
View File

@@ -0,0 +1,13 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t00036_class:
type: class
generate_packages: true
glob:
- ../../tests/t00036/t00036.cc
using_namespace:
- clanguml::t00036
include:
namespaces:
- clanguml::t00036

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

@@ -0,0 +1,33 @@
namespace clanguml {
namespace t00036 {
namespace ns1 {
enum class E { blue, yellow };
namespace ns11 {
template <typename T> struct A {
T a;
};
namespace ns111 {
struct B {
A<int> a_int;
};
}
}
}
namespace ns2 {
namespace ns22 {
struct C;
}
}
} // namespace t00036
} // namespace clanguml

50
tests/t00036/test_case.h Normal file
View File

@@ -0,0 +1,50 @@
/**
* tests/t00036/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("t00036", "[test-case][class]")
{
auto [config, db] = load_config("t00036");
auto diagram = config.diagrams["t00036_class"];
REQUIRE(diagram->name == "t00036_class");
REQUIRE(diagram->generate_packages() == true);
auto model = generate_class_diagram(db, diagram);
REQUIRE(model.name() == "t00036_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, IsClassTemplate("A", "T"));
REQUIRE_THAT(puml, IsClassTemplate("A", "int"));
REQUIRE_THAT(puml, IsEnum(_A("E")));
REQUIRE_THAT(puml, IsClass(_A("B")));
REQUIRE_THAT(puml, IsClass(_A("C")));
REQUIRE_THAT(puml, IsPackage("ns111"));
REQUIRE_THAT(puml, IsPackage("ns22"));
REQUIRE_THAT(puml, IsAggregation(_A("B"), _A("A<int>"), "+a_int"));
save_puml(
"./" + config.output_directory() + "/" + diagram->name + ".puml", puml);
}

View File

@@ -178,6 +178,7 @@ using namespace clanguml::test::matchers;
#include "t00033/test_case.h" #include "t00033/test_case.h"
#include "t00034/test_case.h" #include "t00034/test_case.h"
#include "t00035/test_case.h" #include "t00035/test_case.h"
#include "t00036/test_case.h"
// //
// Sequence diagram tests // Sequence diagram tests

View File

@@ -102,6 +102,9 @@ test_cases:
- name: t00035 - name: t00035
title: PlantUML class diagram layout hints test case title: PlantUML class diagram layout hints test case
description: description:
- name: t00036
title: Class diagram with namespaces generated as packages
description:
Sequence diagrams: Sequence diagrams:
- name: t20001 - name: t20001
title: Basic sequence diagram test case title: Basic sequence diagram test case

View File

@@ -33,6 +33,7 @@ TEST_CASE("Test config simple", "[unit-test]")
CHECK(clanguml::util::contains(diagram.using_namespace(), "clanguml")); CHECK(clanguml::util::contains(diagram.using_namespace(), "clanguml"));
CHECK(diagram.generate_method_arguments() == CHECK(diagram.generate_method_arguments() ==
clanguml::config::method_arguments::full); clanguml::config::method_arguments::full);
CHECK(diagram.generate_packages() == true);
} }
TEST_CASE("Test config inherited", "[unit-test]") TEST_CASE("Test config inherited", "[unit-test]")
@@ -46,6 +47,7 @@ TEST_CASE("Test config inherited", "[unit-test]")
CHECK(def.glob()[0] == "src/**/*.cc"); CHECK(def.glob()[0] == "src/**/*.cc");
CHECK(def.glob()[1] == "src/**/*.h"); CHECK(def.glob()[1] == "src/**/*.h");
CHECK(clanguml::util::contains(def.using_namespace(), "clanguml")); CHECK(clanguml::util::contains(def.using_namespace(), "clanguml"));
CHECK(def.generate_packages() == false);
auto &cus = *cfg.diagrams["class_custom"]; auto &cus = *cfg.diagrams["class_custom"];
CHECK(cus.type() == clanguml::config::diagram_type::class_diagram); CHECK(cus.type() == clanguml::config::diagram_type::class_diagram);
@@ -53,6 +55,7 @@ TEST_CASE("Test config inherited", "[unit-test]")
CHECK(cus.glob()[0] == "src/main.cc"); CHECK(cus.glob()[0] == "src/main.cc");
CHECK(clanguml::util::contains(cus.using_namespace(), "clanguml::ns1")); CHECK(clanguml::util::contains(cus.using_namespace(), "clanguml::ns1"));
CHECK(cus.include_relations_also_as_members()); CHECK(cus.include_relations_also_as_members());
CHECK(def.generate_packages() == false);
} }
TEST_CASE("Test config includes", "[unit-test]") TEST_CASE("Test config includes", "[unit-test]")

View File

@@ -9,6 +9,7 @@ diagrams:
using_namespace: using_namespace:
- clanguml - clanguml
generate_method_arguments: full generate_method_arguments: full
generate_packages: true
include: include:
namespaces: namespaces:
- clanguml - clanguml

View File

@@ -32,6 +32,8 @@ TEST_CASE("Test split", "[unit-test]")
CHECK(split("ABCD", " ") == C{"ABCD"}); CHECK(split("ABCD", " ") == C{"ABCD"});
CHECK(split("std::vector::detail", "::") == C{"std", "vector", "detail"}); CHECK(split("std::vector::detail", "::") == C{"std", "vector", "detail"});
CHECK(split("std::vector::detail::", "::") == C{"std", "vector", "detail"});
} }
TEST_CASE("Test ns_relative", "[unit-test]") TEST_CASE("Test ns_relative", "[unit-test]")