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)
`clang-uml` is an automatic C++ to [PlantUML](https://plantuml.com) class and sequence
diagram generator, driven by YAML configuration files. The main idea behind the
`clang-uml` is an automatic C++ to [PlantUML](https://plantuml.com) class, sequence
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
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
Main features supported so far include:
@@ -17,11 +17,14 @@ Main features supported so far include:
* Template instantiation relationships
* Relationship inference from C++ containers and smart pointers
* Namespace based content filtering
* Optional package generation from namespaces
* 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
* 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
### Building from source

View File

@@ -34,6 +34,7 @@
* [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
* [t00036](./test_cases/t00036.md) - Class diagram with namespaces generated as packages
## Sequence diagrams
* [t20001](./test_cases/t20001.md) - Basic 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,20 +33,30 @@ void generator::generate_alias(const class_ &c, std::ostream &ostr) const
if (c.is_abstract())
class_type = "abstract";
ostr << class_type << " \"" << c.full_name();
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 << "\" as " << c.alias() << '\n';
}
void generator::generate_alias(const enum_ &e, std::ostream &ostr) const
{
ostr << "enum"
<< " \"" << e.full_name();
if (m_config.generate_packages())
ostr << "enum"
<< " \"" << e.name();
else
ostr << "enum"
<< " \"" << e.full_name();
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;
@@ -89,7 +99,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
return mp.to_string(m_config.using_namespace());
});
auto args_string = fmt::format("{}", fmt::join(params, ", "));
if (m_config.generate_method_arguments() !=
if (m_config.generate_method_arguments() ==
config::method_arguments::abbreviated) {
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);
// 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();
@@ -255,7 +266,7 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const
relstr << '\n';
ostr << relstr.str();
relationships_ostr << relstr.str();
}
catch (error::uml_alias_missing &ex) {
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);
}
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
{
ostr << "@startuml" << '\n';
std::stringstream relationship_ostr;
generate_plantuml_directives(ostr, m_config.puml().before);
if (m_config.should_include_entities("classes")) {
for (const auto &c : m_model.classes()) {
if (!m_config.should_include(c.name()))
continue;
generate_alias(c, ostr);
ostr << '\n';
for (const auto &p : m_model) {
if (dynamic_cast<package *>(p.get())) {
generate(dynamic_cast<package &>(*p), ostr, relationship_ostr);
}
for (const auto &e : m_model.enums()) {
if (!m_config.should_include(e.name()))
continue;
generate_alias(e, ostr);
ostr << '\n';
if (dynamic_cast<class_ *>(p.get())) {
generate_alias(dynamic_cast<class_ &>(*p), ostr);
generate(dynamic_cast<class_ &>(*p), ostr, relationship_ostr);
}
for (const auto &c : m_model.classes()) {
if (!m_config.should_include(c.name()))
continue;
generate(c, ostr);
ostr << '\n';
if (dynamic_cast<enum_ *>(p.get())) {
generate_alias(dynamic_cast<enum_ &>(*p), ostr);
generate(dynamic_cast<enum_ &>(*p), ostr, relationship_ostr);
}
ostr << '\n';
}
if (m_config.should_include_entities("enums"))
for (const auto &e : m_model.enums()) {
generate(e, ostr);
ostr << '\n';
}
ostr << relationship_ostr.str();
generate_config_layout_hints(ostr);
@@ -310,5 +353,4 @@ void generator::generate(std::ostream &ostr) const
ostr << "@enduml" << '\n';
}
}

View File

@@ -49,6 +49,7 @@ using common_generator =
using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::enum_;
using clanguml::common::model::package;
using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t;
@@ -62,9 +63,14 @@ public:
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;
};

View File

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

View File

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

View File

@@ -21,42 +21,89 @@
#include "util/error.h"
#include "util/util.h"
#include <cassert>
#include <iostream>
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
{
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());
type_aliases_[ta.alias()] = std::move(ta);
return std::any_of(enums_.cbegin(), enums_.cend(),
[&e](const auto &ee) { return ee.get().full_name() == e.full_name(); });
}
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());
if (!has_class(c))
classes_.emplace_back(std::move(c));
LOG_DBG(
"Adding global alias: {} -> {}", ta->alias(), ta->underlying_type());
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
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());
auto it = std::find(enums_.begin(), enums_.end(), e);
if (it == enums_.end())
enums_.emplace_back(std::move(e));
LOG_DBG("Adding enum: {}", e->name());
assert(!util::contains(e->name(), "::"));
if (!has_enum(*e)) {
enums_.emplace_back(*e);
auto ns = e->get_relative_namespace();
add_element(ns, std::move(e));
}
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
@@ -64,18 +111,20 @@ std::string diagram::to_alias(const std::string &full_name) const
LOG_DBG("Looking for alias for {}", full_name);
for (const auto &c : classes_) {
if (c.full_name() == full_name) {
return c.alias();
const auto &cc = c.get();
if (cc.full_name() == full_name) {
return c->alias();
}
}
for (const auto &e : enums_) {
if (e.full_name() == full_name) {
return e.alias();
if (e.get().full_name() == full_name) {
return e->alias();
}
}
throw error::uml_alias_missing(
fmt::format("Missing alias for {}", full_name));
}
}

View File

@@ -19,6 +19,8 @@
#include "class.h"
#include "common/model/diagram.h"
#include "common/model/nested_trait.h"
#include "common/model/package.h"
#include "enum.h"
#include "type_alias.h"
@@ -27,7 +29,9 @@
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:
diagram() = default;
@@ -36,23 +40,29 @@ public:
diagram &operator=(const diagram &) = delete;
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;
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;
friend void print_diagram_tree(const diagram &d, const int level);
private:
std::vector<class_> classes_;
std::vector<enum_> enums_;
std::map<std::string, type_alias> type_aliases_;
std::vector<type_safe::object_ref<const class_, false>> classes_;
std::vector<type_safe::object_ref<const enum_, false>> enums_;
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
{

View File

@@ -29,6 +29,12 @@ class enum_ : public common::model::element,
public:
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);
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(
const std::string &full_name) const
{
@@ -132,4 +179,16 @@ clanguml::class_diagram::model::diagram &translation_unit_context::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 <cppast/cpp_entity_index.hpp>
#include <cppast/cpp_namespace.hpp>
#include <cppast/cpp_type.hpp>
#include <type_safe/reference.hpp>
@@ -31,6 +32,17 @@ public:
clanguml::class_diagram::model::diagram &diagram,
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;
void add_type_alias(const std::string &full_name,
@@ -62,6 +74,10 @@ public:
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:
// Current visitor namespace
std::vector<std::string> namespace_;
@@ -75,6 +91,10 @@ private:
// Reference to 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)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
alias_index_;
@@ -82,6 +102,8 @@ private:
// Map of discovered template aliases (declared with 'using' keyword)
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
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 <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 <map>
#include <memory>
@@ -41,6 +46,9 @@
namespace clanguml::class_diagram::visitor {
using found_relationships_t =
std::vector<std::pair<std::string, common::model::relationship_t>>;
class translation_unit_visitor {
public:
translation_unit_visitor(cppast::cpp_entity_index &idx,
@@ -99,8 +107,7 @@ public:
const std::set<std::string> &template_parameter_names = {});
bool find_relationships(const cppast::cpp_type &t,
std::vector<std::pair<std::string,
clanguml::common::model::relationship_t>> &relationships,
found_relationships_t &relationships,
clanguml::common::model::relationship_t relationship_hint =
clanguml::common::model::relationship_t::kNone);
@@ -119,8 +126,42 @@ public:
void process_friend(const cppast::cpp_friend &t,
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:
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,
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_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
translation_unit_context ctx;
};

View File

@@ -20,6 +20,8 @@
#include "util/util.h"
#include <ostream>
namespace clanguml::common::model {
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}
, 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); }
@@ -41,6 +45,13 @@ void element::add_relationship(relationship &&cr)
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(),
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)
{
for (const auto &n : un)
assert(!util::contains(n, "::"));
using_namespaces_ = un;
}
@@ -66,4 +80,19 @@ const std::vector<relationship> &element::relationships() const
}
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 "relationship.h"
#include "util/util.h"
#include <atomic>
#include <exception>
#include <string>
#include <vector>
@@ -30,16 +32,32 @@ class element : public decorated_element {
public:
element(const std::vector<std::string> &using_namespaces);
virtual ~element() = default;
std::string alias() const;
void set_name(const std::string &name) { name_ = 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; }
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(); }
void set_using_namespaces(const std::vector<std::string> &un);
@@ -54,6 +72,10 @@ public:
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:
const uint64_t m_id{0};

View File

@@ -21,6 +21,7 @@
#include <type_safe/optional_ref.hpp>
#include <iostream>
#include <string>
#include <vector>
@@ -37,7 +38,7 @@ public:
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(),
[&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);
LOG_DBG("Adding nested element {} at path '{}'", p->name(),
LOG_DBG("Adding nested element {} at path {}", p->name(),
fmt::join(path, "::"));
if (path.empty()) {
@@ -64,42 +66,57 @@ public:
auto parent = get_element(path);
if (parent)
parent.value().add_element(std::move(p));
else
if (parent && dynamic_cast<nested_trait<T> *>(&parent.value()))
dynamic_cast<nested_trait<T> &>(parent.value())
.template add_element<V>(std::move(p));
else {
spdlog::error(
"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, "::"));
if (path.empty() || !has_element(path.at(0))) {
LOG_WARN("Nested element {} not found in element",
fmt::join(path, "::"));
return {};
return type_safe::optional_ref<V>{};
}
auto p = get_element(path.at(0));
if (path.size() == 1)
return p;
return get_element<V>(path.at(0));
return p.value().get_element(
std::vector<std::string>(path.begin() + 1, path.end()));
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()));
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(),
[&](const auto &p) { return name == p->name(); });
if (it == elements_.end())
return {};
return type_safe::optional_ref<V>{type_safe::nullopt};
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
@@ -118,6 +135,24 @@ public:
auto begin() const { return elements_.begin(); }
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:
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>
*
@@ -22,7 +22,7 @@
#include <sstream>
namespace clanguml::package_diagram::model {
namespace clanguml::common::model {
package::package(const std::vector<std::string> &using_namespaces)
: element{using_namespaces}
{
@@ -43,11 +43,6 @@ std::string package::full_name(bool relative) const
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_; }
void package::set_deprecated(bool deprecated) { is_deprecated_ = deprecated; }

View File

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

View File

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

View File

@@ -87,6 +87,7 @@ struct inheritable_diagram_options {
option<plantuml> puml{"plantuml", option_inherit_mode::append};
option<method_arguments> generate_method_arguments{
"generate_method_arguments", method_arguments::full};
option<bool> generate_packages{"generate_packages", false};
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(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 std::string &name_) const;
private:
};
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; }
const T &operator()() const { return value; }

View File

@@ -126,6 +126,16 @@ bool is_inside_class(const cppast::cpp_entity &e)
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)
{
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::pair<std::vector<std::string>, std::string> split_ns(
const std::string &full_name);
bool is_inside_class(const cppast::cpp_entity &e);
} // namespace util
} // namespace cx

View File

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

View File

@@ -18,11 +18,11 @@
#pragma once
#include "common/generators/plantuml/generator.h"
#include "common/model/package.h"
#include "common/model/relationship.h"
#include "config/config.h"
#include "cx/compilation_database.h"
#include "package_diagram/model/diagram.h"
#include "package_diagram/model/package.h"
#include "package_diagram/visitor/translation_unit_visitor.h"
#include "util/util.h"
@@ -46,9 +46,9 @@ template <typename C, typename D>
using common_generator =
clanguml::common::generators::plantuml::generator<C, D>;
using clanguml::common::model::package;
using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t;
using clanguml::package_diagram::model::package;
using namespace clanguml::util;
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(
fmt::format("Missing alias for '{}'", full_name));
auto package = get_element(fn);
auto package = get_element<common::model::package>(fn);
if (!package)
throw error::uml_alias_missing(

View File

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

View File

@@ -180,12 +180,12 @@ clanguml::package_diagram::model::diagram &translation_unit_context::diagram()
}
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;
}
type_safe::optional_ref<model::package>
type_safe::optional_ref<common::model::package>
translation_unit_context::get_current_package() const
{
return current_package_;

View File

@@ -17,6 +17,7 @@
*/
#pragma once
#include "common/model/package.h"
#include "config/config.h"
#include "package_diagram/model/diagram.h"
@@ -75,9 +76,9 @@ public:
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:
// Current visitor namespace
@@ -104,7 +105,7 @@ private:
std::map<std::string, type_safe::object_ref<const cppast::cpp_type>>
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::common::model::access_t;
using clanguml::common::model::package;
using clanguml::common::model::relationship;
using clanguml::common::model::relationship_t;
using clanguml::common::model::scope_t;
using clanguml::package_diagram::model::diagram;
using clanguml::package_diagram::model::package;
namespace detail {
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], "::");
if (!util::starts_with(usn, package_path)) {
auto p = std::make_unique<package>(
ctx.config().using_namespace());
auto p = std::make_unique<package>(usn);
util::remove_prefix(package_path, usn);
util::remove_prefix(package_parent, usn);
p->set_name(e.name());
p->set_namespace(package_parent);
@@ -122,10 +120,11 @@ void translation_unit_visitor::operator()(const cppast::cpp_entity &file)
}
if (!p->skip()) {
ctx.diagram().add_element(
package_parent, std::move(p));
auto rns = p->get_relative_namespace();
ctx.diagram().add_element(rns, std::move(p));
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(
resolve_alias(cppast::remove_cv(t_)), ctx.entity_index(), false);
auto t_ns = util::split(fn, "::");
auto t_name = t_ns.back();
t_ns.pop_back();
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,
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: {} | {}",
cppast::to_string(t_), cppast::to_string(t_.canonical()));

View File

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

View File

@@ -80,7 +80,8 @@ void translation_unit_visitor::process_activities(const cppast::cpp_function &e)
.value();
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;
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)
m.to += "()";
if (!ctx.config().should_include(m.to))
if (!ctx.config().should_include(
util::split(cx::util::ns(callee), "::"), callee.name()))
continue;
m.to_usr = type_safe::get(function_call.get_callee_method_id());

View File

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

View File

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

View File

@@ -30,8 +30,8 @@ TEST_CASE("t00002", "[test-case][class]")
REQUIRE(diagram->exclude().namespaces.size() == 0);
REQUIRE(diagram->should_include("clanguml::t00002::A"));
REQUIRE(!diagram->should_include("std::vector"));
REQUIRE(diagram->should_include({"clanguml", "t00002"}, "A"));
REQUIRE(!diagram->should_include({"std"}, "vector"));
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", "bool,std::string"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "float"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "int"));
REQUIRE_THAT(puml, IsClassTemplate("AString", "std::string"));
REQUIRE_THAT(
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,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(
puml, IsAggregation(_A("R"), _A("A<bool,std::string>"), "-boolstring"));
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 "t00034/test_case.h"
#include "t00035/test_case.h"
#include "t00036/test_case.h"
//
// Sequence diagram tests

View File

@@ -102,6 +102,9 @@ test_cases:
- name: t00035
title: PlantUML class diagram layout hints test case
description:
- name: t00036
title: Class diagram with namespaces generated as packages
description:
Sequence diagrams:
- name: t20001
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(diagram.generate_method_arguments() ==
clanguml::config::method_arguments::full);
CHECK(diagram.generate_packages() == true);
}
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()[1] == "src/**/*.h");
CHECK(clanguml::util::contains(def.using_namespace(), "clanguml"));
CHECK(def.generate_packages() == false);
auto &cus = *cfg.diagrams["class_custom"];
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(clanguml::util::contains(cus.using_namespace(), "clanguml::ns1"));
CHECK(cus.include_relations_also_as_members());
CHECK(def.generate_packages() == false);
}
TEST_CASE("Test config includes", "[unit-test]")

View File

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

View File

@@ -32,6 +32,8 @@ TEST_CASE("Test split", "[unit-test]")
CHECK(split("ABCD", " ") == C{"ABCD"});
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]")