Compare commits

..

16 Commits

Author SHA1 Message Date
Bartek Kryza
f97d42083b Enabled multiple relative link patterns in generate_links option (#297) 2024-07-28 18:33:58 +02:00
Bartek Kryza
37314baa3a Refactored inja context evaluation and link rendering 2024-07-28 01:12:24 +02:00
Bartek Kryza
dad583d305 Enabled accessing comments from system headers in sequence diagrams 2024-07-26 18:10:05 +02:00
Bartek Kryza
a319bd0ede Enabled advanced diagram filters in sequence diagrams (#289) 2024-07-25 13:50:51 +02:00
Bartek Kryza
d5687907e0 Fixed tests on macos (#248) 2024-07-24 13:16:47 +02:00
Bartek Kryza
077e1c1e70 Extended test case t00080 with system header comment 2024-07-23 17:05:17 +02:00
Bartek Kryza
2004d25bdd Refactored class, include and package diagrams visitor to output filtered intermediate diagram (#289) 2024-07-23 16:32:15 +02:00
Bartek Kryza
cadbeba82c Add more advanced mode diagram filter test cases 2024-07-09 18:23:10 +02:00
Bartek Kryza
01c6264923 Fixed plantuml_sequence_diagram_generator_sequence diagram generation 2024-06-28 19:20:36 +02:00
Bartek Kryza
4c87c01de2 Added test case for relationships to std types 2024-06-28 00:23:56 +02:00
Bartek Kryza
f47d11943e Added option include_system_headers to enable processing of system headers 2024-06-27 10:56:36 +02:00
Bartek Kryza
fbe3631cff Refactored advanced diagram filter (#289) 2024-06-26 13:00:56 +02:00
Bartek Kryza
40851b395e Added initial version of advanced diagram filter config (#289) 2024-06-25 17:26:23 +02:00
Bartek Kryza
19bb8ae1ca Refactored diagram_filter initialization to a factory (#289) 2024-06-24 19:40:30 +02:00
Bartek Kryza
c0f5d5f64a Added CLANG_UML_ENABLE_BACKTRACE CMake option to enable backtrace in release builds (#292) 2024-06-21 23:52:33 +02:00
Bartek Kryza
2c23627154 Merge pull request #291 from bkryza/v0.5.3
V0.5.3
2024-06-19 21:58:11 +02:00
76 changed files with 2697 additions and 1213 deletions

View File

@@ -133,14 +133,25 @@ endif()
link_directories(${LLVM_LIBRARY_DIR} ${YAML_CPP_LIBRARY_DIR})
#
# Setup libdw and libunwind
# Setup backward-cpp, libdw and libunwind
#
if(LINUX AND (CMAKE_BUILD_TYPE MATCHES Debug))
if(CMAKE_BUILD_TYPE MATCHES Debug)
set(CLANG_UML_ENABLE_BACKTRACE_DEFAULT ON)
else()
set(CLANG_UML_ENABLE_BACKTRACE_DEFAULT OFF)
endif()
option(CLANG_UML_ENABLE_BACKTRACE "Enable backtrace on segfault"
${CLANG_UML_ENABLE_BACKTRACE_DEFAULT})
if(LINUX AND CLANG_UML_ENABLE_BACKTRACE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DW REQUIRED IMPORTED_TARGET libdw)
pkg_check_modules(UNWIND REQUIRED IMPORTED_TARGET libunwind)
set(BACKWARD_CPP_LIBRARIES PkgConfig::DW PkgConfig::UNWIND)
set(ENABLE_BACKWARD_CPP -DENABLE_BACKWARD_CPP)
message(STATUS "Enabling backward-cpp")
else()
message(STATUS "Disabling backward-cpp")
endif()
#

View File

@@ -54,6 +54,7 @@ clean:
rm -rf debug release debug_tidy coverage.info coverage-src.info
debug/CMakeLists.txt:
$(eval CLANG_UML_ENABLE_BACKTRACE ?= ON)
cmake -S . -B debug \
-G"$(CMAKE_GENERATOR)" \
-DGIT_VERSION=$(GIT_VERSION) \
@@ -67,9 +68,11 @@ debug/CMakeLists.txt:
-DCMAKE_PREFIX=${CMAKE_PREFIX} \
-DENABLE_CUDA_TEST_CASES=$(ENABLE_CUDA_TEST_CASES) \
-DENABLE_CXX_MODULES_TEST_CASES=$(ENABLE_CXX_MODULES_TEST_CASES) \
-DCODE_COVERAGE=$(CODE_COVERAGE)
-DCODE_COVERAGE=$(CODE_COVERAGE) \
-DCLANG_UML_ENABLE_BACKTRACE=$(CLANG_UML_ENABLE_BACKTRACE)
release/CMakeLists.txt:
$(eval CLANG_UML_ENABLE_BACKTRACE ?= OFF)
cmake -S . -B release \
-G"$(CMAKE_GENERATOR)" \
-DGIT_VERSION=$(GIT_VERSION) \
@@ -82,7 +85,8 @@ release/CMakeLists.txt:
-DLINK_LLVM_SHARED=${LLVM_SHARED} \
-DCMAKE_PREFIX=${CMAKE_PREFIX} \
-DENABLE_CUDA_TEST_CASES=$(ENABLE_CUDA_TEST_CASES) \
-DENABLE_CXX_MODULES_TEST_CASES=$(ENABLE_CXX_MODULES_TEST_CASES)
-DENABLE_CXX_MODULES_TEST_CASES=$(ENABLE_CXX_MODULES_TEST_CASES) \
-DCLANG_UML_ENABLE_BACKTRACE=$(CLANG_UML_ENABLE_BACKTRACE)
debug_tidy/CMakeLists.txt:
cmake -S . -B debug_tidy \

View File

@@ -158,26 +158,17 @@ void generator::generate_top_level_elements(nlohmann::json &parent) const
{
for (const auto &p : model()) {
if (auto *pkg = dynamic_cast<package *>(p.get()); pkg) {
if (!pkg->is_empty() &&
!pkg->all_of([this](const common::model::element &e) {
return !model().should_include(e);
}))
if (!pkg->is_empty())
generate(*pkg, parent);
}
else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
if (model().should_include(*cls)) {
generate(*cls, parent);
}
generate(*cls, parent);
}
else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
if (model().should_include(*enm)) {
generate(*enm, parent);
}
generate(*enm, parent);
}
else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
if (model().should_include(*cpt)) {
generate(*cpt, parent);
}
generate(*cpt, parent);
}
}
}
@@ -203,10 +194,7 @@ void generator::generate(const package &p, nlohmann::json &parent) const
for (const auto &subpackage : p) {
if (dynamic_cast<package *>(subpackage.get()) != nullptr) {
const auto &sp = dynamic_cast<package &>(*subpackage);
if (!sp.is_empty() &&
!sp.all_of([this](const common::model::element &e) {
return !model().should_include(e);
})) {
if (!sp.is_empty()) {
if (config().generate_packages())
generate(sp, package_object);
else
@@ -214,28 +202,22 @@ void generator::generate(const package &p, nlohmann::json &parent) const
}
}
else if (auto *cls = dynamic_cast<class_ *>(subpackage.get()); cls) {
if (model().should_include(*cls)) {
if (config().generate_packages())
generate(*cls, package_object);
else
generate(*cls, parent);
}
if (config().generate_packages())
generate(*cls, package_object);
else
generate(*cls, parent);
}
else if (auto *enm = dynamic_cast<enum_ *>(subpackage.get()); enm) {
if (model().should_include(*enm)) {
if (config().generate_packages())
generate(*enm, package_object);
else
generate(*enm, parent);
}
if (config().generate_packages())
generate(*enm, package_object);
else
generate(*enm, parent);
}
else if (auto *cpt = dynamic_cast<concept_ *>(subpackage.get()); cpt) {
if (model().should_include(*cpt)) {
if (config().generate_packages())
generate(*cpt, package_object);
else
generate(*cpt, parent);
}
if (config().generate_packages())
generate(*cpt, package_object);
else
generate(*cpt, parent);
}
}
@@ -253,6 +235,9 @@ void generator::generate(const class_ &c, nlohmann::json &parent) const
object["display_name"] =
common::generators::json::render_name(c.full_name_no_ns());
object["display_name"] =
config().simplify_template_type(object["display_name"]);
for (auto &tp : object["template_parameters"]) {
if (tp.contains("type") && tp.at("type").is_string()) {
tp["type"] = config().using_namespace().relative(tp.at("type"));
@@ -298,19 +283,13 @@ void generator::generate_relationships(nlohmann::json &parent) const
generate_relationships(*pkg, parent);
}
else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
if (model().should_include(*cls)) {
generate_relationships(*cls, parent);
}
generate_relationships(*cls, parent);
}
else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
if (model().should_include(*enm)) {
generate_relationships(*enm, parent);
}
generate_relationships(*enm, parent);
}
else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
if (model().should_include(*cpt)) {
generate_relationships(*cpt, parent);
}
generate_relationships(*cpt, parent);
}
}
}
@@ -319,9 +298,6 @@ void generator::generate_relationships(
const class_ &c, nlohmann::json &parent) const
{
for (const auto &r : c.relationships()) {
if (!model().should_include(r))
continue;
auto target_element = model().get(r.destination());
if (!target_element.has_value()) {
LOG_DBG("Skipping {} relation from {} to {} due "
@@ -350,9 +326,6 @@ void generator::generate_relationships(
const enum_ &c, nlohmann::json &parent) const
{
for (const auto &r : c.relationships()) {
if (!model().should_include(r))
continue;
auto target_element = model().get(r.destination());
if (!target_element.has_value()) {
LOG_DBG("Skipping {} relation from {} to {} due "
@@ -371,9 +344,6 @@ void generator::generate_relationships(
const concept_ &c, nlohmann::json &parent) const
{
for (const auto &r : c.relationships()) {
if (!model().should_include(r))
continue;
auto target_element = model().get(r.destination());
if (!target_element.has_value()) {
LOG_DBG("Skipping {} relation from {} to {} due "

View File

@@ -25,6 +25,7 @@
namespace clanguml::class_diagram::generators::mermaid {
using clanguml::common::eid_t;
using clanguml::common::generators::mermaid::escape_name;
using clanguml::common::generators::mermaid::indent;
using clanguml::common::generators::mermaid::render_name;
@@ -50,8 +51,8 @@ void generator::generate_alias(
auto class_label = config().simplify_template_type(render_name(full_name));
ostr << indent(1) << "class " << c.alias() << "[\"" << class_label
<< "\"]\n";
ostr << indent(1) << "class " << c.alias() << "[\""
<< escape_name(class_label) << "\"]\n";
// Register the added alias
m_generated_aliases.emplace(c.alias());
@@ -88,9 +89,6 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
std::stringstream all_relations_str;
for (const auto &r : c.relationships()) {
if (!model().should_include(r.type()))
continue;
try {
generate_relationship(r, rendered_relations);
}
@@ -110,9 +108,6 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
sort_class_elements(members);
for (const auto &m : members) {
if (!model().should_include(m))
continue;
if (!config().include_relations_also_as_members() &&
rendered_relations.find(m.name()) != rendered_relations.end())
continue;
@@ -155,11 +150,7 @@ void generator::generate_methods(
sort_class_elements(sorted_methods);
for (const auto &m : sorted_methods) {
if (!model().should_include(m))
continue;
generate_method(m, ostr);
ostr << '\n';
}
}
@@ -169,17 +160,11 @@ generator::method_groups_t generator::group_methods(
{
std::map<std::string, std::vector<class_method>> result;
// First get rid of methods which don't pass the filters
std::vector<class_method> filtered_methods;
std::copy_if(methods.cbegin(), methods.cend(),
std::back_inserter(filtered_methods),
[this](auto &m) { return model().should_include(m); });
for (const auto &g : method_groups_) {
result[g] = {};
}
for (const auto &m : filtered_methods) {
for (const auto &m : methods) {
if (m.is_constructor() || m.is_destructor()) {
result["constructors"].push_back(m);
}
@@ -257,7 +242,7 @@ void generator::generate_method(
ostr << fmt::format("[{}] ", fmt::join(method_mods, ","));
}
ostr << render_name(type);
ostr << escape_name(render_name(type));
if (m.is_pure_virtual())
ostr << "*";
@@ -276,8 +261,8 @@ void generator::generate_member(
ostr << indent(2) << mermaid_common::to_mermaid(m.access()) << m.name()
<< " : "
<< render_name(
uns.relative(config().simplify_template_type(m.type())));
<< escape_name(uns.relative(
config().simplify_template_type(render_name(m.type()))));
}
void generator::generate(const concept_ &c, std::ostream &ostr) const
@@ -294,7 +279,7 @@ void generator::generate(const concept_ &c, std::ostream &ostr) const
parameters.reserve(c.requires_parameters().size());
for (const auto &p : c.requires_parameters()) {
parameters.emplace_back(
render_name(p.to_string(config().using_namespace())));
escape_name(p.to_string(config().using_namespace())));
}
ostr << indent(2)
@@ -302,7 +287,7 @@ void generator::generate(const concept_ &c, std::ostream &ostr) const
for (const auto &req : c.requires_statements()) {
ostr << indent(2)
<< fmt::format("\"{}\"\n", render_name(req, false));
<< fmt::format("\"{}\"\n", escape_name(req, false));
}
}
@@ -332,19 +317,13 @@ void generator::generate_relationships(std::ostream &ostr) const
generate_relationships(*pkg, ostr);
}
else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
if (model().should_include(*cls)) {
generate_relationships(*cls, ostr);
}
generate_relationships(*cls, ostr);
}
else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
if (model().should_include(*enm)) {
generate_relationships(*enm, ostr);
}
generate_relationships(*enm, ostr);
}
else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
if (model().should_include(*cpt)) {
generate_relationships(*cpt, ostr);
}
generate_relationships(*cpt, ostr);
}
}
}
@@ -400,9 +379,6 @@ void generator::generate_relationships(
std::set<std::string> unique_relations;
for (const auto &r : c.relationships()) {
if (!model().should_include(r.type()))
continue;
LOG_DBG("== Processing relationship {}", to_string(r.type()));
std::stringstream relstr;
@@ -508,9 +484,6 @@ void generator::generate_relationships(
std::set<std::string> unique_relations;
for (const auto &r : c.relationships()) {
if (!model().should_include(r.type()))
continue;
LOG_DBG("== Processing relationship {}", to_string(r.type()));
std::stringstream relstr;
@@ -582,9 +555,6 @@ void generator::generate_relationships(
void generator::generate_relationships(const enum_ &e, std::ostream &ostr) const
{
for (const auto &r : e.relationships()) {
if (!model().should_include(r.type()))
continue;
eid_t destination{};
std::stringstream relstr;
try {
@@ -717,10 +687,7 @@ void generator::generate_relationships(
// packages which do not contain anything but other
// packages are skipped
const auto &sp = dynamic_cast<package &>(*subpackage);
if (!sp.is_empty() &&
!sp.all_of([this](const common::model::element &e) {
return !model().should_include(e);
}))
if (!sp.is_empty())
generate_relationships(sp, ostr);
}
else if (dynamic_cast<class_ *>(subpackage.get()) != nullptr) {
@@ -757,52 +724,43 @@ void generator::generate_top_level_elements(std::ostream &ostr) const
{
for (const auto &p : model()) {
if (auto *pkg = dynamic_cast<package *>(p.get()); pkg) {
if (!pkg->is_empty() &&
!pkg->all_of([this](const common::model::element &e) {
return !model().should_include(e);
}))
if (!pkg->is_empty())
generate(*pkg, ostr);
}
else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
if (model().should_include(*cls)) {
auto together_group =
config().get_together_group(cls->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cls);
}
else {
generate_alias(*cls, ostr);
generate(*cls, ostr);
}
auto together_group =
config().get_together_group(cls->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cls);
}
else {
generate_alias(*cls, ostr);
generate(*cls, ostr);
}
}
else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
if (model().should_include(*enm)) {
auto together_group =
config().get_together_group(enm->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), enm);
}
else {
generate_alias(*enm, ostr);
generate(*enm, ostr);
}
auto together_group =
config().get_together_group(enm->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), enm);
}
else {
generate_alias(*enm, ostr);
generate(*enm, ostr);
}
}
else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
if (model().should_include(*cpt)) {
auto together_group =
config().get_together_group(cpt->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cpt);
}
else {
generate_alias(*cpt, ostr);
generate(*cpt, ostr);
}
auto together_group =
config().get_together_group(cpt->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cpt);
}
else {
generate_alias(*cpt, ostr);
generate(*cpt, ostr);
}
}
}

View File

@@ -38,16 +38,24 @@ void generator::generate_link(
auto context = element_context(e);
if (!config().generate_links().link.empty()) {
auto maybe_link_pattern = get_link_pattern(e);
if (maybe_link_pattern) {
const auto &[link_prefix, link_pattern] = *maybe_link_pattern;
auto ec = element_context(e);
common::generators::make_context_source_relative(ec, link_prefix);
ostr << " [[[";
ostr << env().render(
std::string_view{config().generate_links().link}, context);
ostr << env().render(std::string_view{link_pattern}, context);
}
if (!config().generate_links().tooltip.empty()) {
auto maybe_tooltip_pattern = get_tooltip_pattern(e);
if (maybe_tooltip_pattern) {
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
auto ec = element_context(e);
common::generators::make_context_source_relative(ec, tooltip_prefix);
ostr << "{";
ostr << env().render(
std::string_view{config().generate_links().tooltip}, context);
ostr << env().render(std::string_view{tooltip_pattern}, ec);
ostr << "}";
}
ostr << "]]]";
@@ -157,9 +165,6 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
std::stringstream all_relations_str;
for (const auto &r : c.relationships()) {
if (!model().should_include(r.type()))
continue;
try {
generate_relationship(r, rendered_relations);
}
@@ -182,9 +187,6 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
ostr << "__\n";
for (const auto &m : members) {
if (!model().should_include(m))
continue;
if (!config().include_relations_also_as_members() &&
rendered_relations.find(m.name()) != rendered_relations.end())
continue;
@@ -228,11 +230,7 @@ void generator::generate_methods(
sort_class_elements(sorted_methods);
for (const auto &m : sorted_methods) {
if (!model().should_include(m))
continue;
generate_method(m, ostr);
ostr << '\n';
}
}
@@ -242,17 +240,11 @@ generator::method_groups_t generator::group_methods(
{
std::map<std::string, std::vector<class_method>> result;
// First get rid of methods which don't pass the filters
std::vector<class_method> filtered_methods;
std::copy_if(methods.cbegin(), methods.cend(),
std::back_inserter(filtered_methods),
[this](auto &m) { return model().should_include(m); });
for (const auto &g : method_groups_) {
result[g] = {};
}
for (const auto &m : filtered_methods) {
for (const auto &m : methods) {
if (m.is_constructor() || m.is_destructor()) {
result["constructors"].push_back(m);
}
@@ -417,19 +409,13 @@ void generator::generate_relationships(std::ostream &ostr) const
generate_relationships(*pkg, ostr);
}
else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
if (model().should_include(*cls)) {
generate_relationships(*cls, ostr);
}
generate_relationships(*cls, ostr);
}
else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
if (model().should_include(*enm)) {
generate_relationships(*enm, ostr);
}
generate_relationships(*enm, ostr);
}
else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
if (model().should_include(*cpt)) {
generate_relationships(*cpt, ostr);
}
generate_relationships(*cpt, ostr);
}
}
}
@@ -481,9 +467,6 @@ void generator::generate_relationships(
std::set<std::string> unique_relations;
for (const auto &r : c.relationships()) {
if (!model().should_include(r.type()))
continue;
LOG_DBG("== Processing relationship {}",
plantuml_common::to_plantuml(r, config()));
@@ -661,9 +644,6 @@ void generator::generate(const enum_ &e, std::ostream &ostr) const
void generator::generate_relationships(const enum_ &e, std::ostream &ostr) const
{
for (const auto &r : e.relationships()) {
if (!model().should_include(r.type()))
continue;
eid_t destination{};
std::stringstream relstr;
try {
@@ -724,10 +704,7 @@ void generator::generate(const package &p, std::ostream &ostr) const
if (dynamic_cast<package *>(subpackage.get()) != nullptr) {
// TODO: add option - generate_empty_packages
const auto &sp = dynamic_cast<package &>(*subpackage);
if (!sp.is_empty() &&
!sp.all_of([this](const common::model::element &e) {
return !model().should_include(e);
})) {
if (!sp.is_empty()) {
together_group_stack_.enter();
generate(sp, ostr);
@@ -822,10 +799,7 @@ void generator::generate_relationships(
// packages which do not contain anything but other
// packages are skipped
const auto &sp = dynamic_cast<package &>(*subpackage);
if (!sp.is_empty() &&
!sp.all_of([this](const common::model::element &e) {
return !model().should_include(e);
}))
if (!sp.is_empty())
generate_relationships(sp, ostr);
}
else if (dynamic_cast<class_ *>(subpackage.get()) != nullptr) {
@@ -864,52 +838,43 @@ void generator::generate_top_level_elements(std::ostream &ostr) const
{
for (const auto &p : model()) {
if (auto *pkg = dynamic_cast<package *>(p.get()); pkg) {
if (!pkg->is_empty() &&
!pkg->all_of([this](const common::model::element &e) {
return !model().should_include(e);
}))
if (!pkg->is_empty())
generate(*pkg, ostr);
}
else if (auto *cls = dynamic_cast<class_ *>(p.get()); cls) {
if (model().should_include(*cls)) {
auto together_group =
config().get_together_group(cls->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cls);
}
else {
generate_alias(*cls, ostr);
generate(*cls, ostr);
}
auto together_group =
config().get_together_group(cls->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cls);
}
else {
generate_alias(*cls, ostr);
generate(*cls, ostr);
}
}
else if (auto *enm = dynamic_cast<enum_ *>(p.get()); enm) {
if (model().should_include(*enm)) {
auto together_group =
config().get_together_group(enm->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), enm);
}
else {
generate_alias(*enm, ostr);
generate(*enm, ostr);
}
auto together_group =
config().get_together_group(enm->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), enm);
}
else {
generate_alias(*enm, ostr);
generate(*enm, ostr);
}
}
else if (auto *cpt = dynamic_cast<concept_ *>(p.get()); cpt) {
if (model().should_include(*cpt)) {
auto together_group =
config().get_together_group(cpt->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cpt);
}
else {
generate_alias(*cpt, ostr);
generate(*cpt, ostr);
}
auto together_group =
config().get_together_group(cpt->full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), cpt);
}
else {
generate_alias(*cpt, ostr);
generate(*cpt, ostr);
}
}
}

View File

@@ -18,6 +18,7 @@
#include "class.h"
#include "common/model/filters/diagram_filter.h"
#include "util/util.h"
#include <sstream>
@@ -63,6 +64,7 @@ const std::vector<class_member> &class_::members() const { return members_; }
const std::vector<class_method> &class_::methods() const { return methods_; }
const std::vector<class_parent> &class_::parents() const { return bases_; }
std::vector<class_parent> &class_::parents() { return bases_; }
bool operator==(const class_ &l, const class_ &r) { return l.id() == r.id(); }
@@ -111,6 +113,21 @@ bool class_::is_abstract() const
[](const auto &method) { return method.is_pure_virtual(); });
}
void class_::apply_filter(
const common::model::diagram_filter &filter, const std::set<eid_t> &removed)
{
diagram_element::apply_filter(filter, removed);
common::model::apply_filter(members_, filter);
common::model::apply_filter(methods_, filter);
// Remove class bases which are no longer in the diagram
parents().erase(
std::remove_if(parents().begin(), parents().end(),
[&removed](auto &&p) { return removed.count(p.id()) > 0; }),
parents().end());
}
std::optional<std::string> class_::doxygen_link() const
{
const auto *type = is_struct() ? "struct" : "class";

View File

@@ -30,6 +30,10 @@
#include <string>
#include <vector>
namespace clanguml::common::model {
class diagram_filter;
}
namespace clanguml::class_diagram::model {
/**
@@ -126,6 +130,7 @@ public:
* @return Reference to class parents.
*/
const std::vector<class_parent> &parents() const;
std::vector<class_parent> &parents();
/**
* @brief Get class full name.
@@ -166,6 +171,9 @@ public:
*/
std::optional<std::string> doxygen_link() const override;
void apply_filter(const common::model::diagram_filter &filter,
const std::set<eid_t> &removed) override;
private:
bool is_struct_{false};
bool is_union_{false};

View File

@@ -18,7 +18,7 @@
#include "diagram.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter.h"
#include "util/error.h"
#include "util/util.h"
@@ -256,6 +256,39 @@ void diagram::remove_redundant_dependencies()
}
}
void diagram::apply_filter()
{
// First find all element ids which should be removed
std::set<eid_t> to_remove;
for (const auto &c : element_view<class_>::view())
if (!filter().should_include(c.get()))
to_remove.emplace(c.get().id());
for (const auto &e : element_view<enum_>::view())
if (!filter().should_include(e.get()))
to_remove.emplace(e.get().id());
for (const auto &c : element_view<concept_>::view())
if (!filter().should_include(c.get()))
to_remove.emplace(c.get().id());
element_view<class_>::remove(to_remove);
element_view<enum_>::remove(to_remove);
element_view<concept_>::remove(to_remove);
nested_trait_ns::remove(to_remove);
for (auto &c : element_view<class_>::view())
c.get().apply_filter(filter(), to_remove);
for (auto &e : element_view<enum_>::view())
e.get().apply_filter(filter(), to_remove);
for (auto &c : element_view<concept_>::view())
c.get().apply_filter(filter(), to_remove);
}
bool diagram::is_empty() const
{
return element_view<class_>::is_empty() &&

View File

@@ -256,6 +256,8 @@ public:
*/
bool is_empty() const override;
void apply_filter() override;
private:
template <typename ElementT>
bool add_with_namespace_path(std::unique_ptr<ElementT> &&e);

View File

@@ -76,7 +76,8 @@ bool translation_unit_visitor::VisitNamespaceDecl(clang::NamespaceDecl *ns)
p->set_id(common::to_id(*ns));
id_mapper().add(ns->getID(), p->id());
if (diagram().should_include(*p) && !diagram().get(p->id())) {
if (config().filter_mode() == config::filter_mode_t::advanced ||
(diagram().should_include(*p) && !diagram().get(p->id()))) {
process_comment(*ns, *p);
set_source_location(*ns, *p);
@@ -667,10 +668,6 @@ void translation_unit_visitor::process_concept_specialization_relationships(
bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
{
// Skip system headers
if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
return true;
if (!should_include(cls))
return true;

View File

@@ -941,12 +941,27 @@ bool parse_source_location(const std::string &location_str, std::string &file,
clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm,
const clang::ASTContext &context, const clang::Stmt *stmt)
{
// First get the first line of the expression
auto expr_begin = stmt->getSourceRange().getBegin();
return get_raw_comment(sm, context, stmt->getSourceRange());
}
clang::RawComment *get_declaration_raw_comment(const clang::SourceManager &sm,
const clang::ASTContext &context, const clang::Decl *decl)
{
return get_raw_comment(sm, context, decl->getSourceRange());
}
clang::RawComment *get_raw_comment(const clang::SourceManager &sm,
const clang::ASTContext &context, const clang::SourceRange &source_range)
{
auto expr_begin = source_range.getBegin();
const auto expr_begin_line = sm.getSpellingLineNumber(expr_begin);
std::string file_Path = sm.getFilename(expr_begin).str();
auto file_id = sm.getFileID(expr_begin);
if (!context.Comments.empty() &&
context.Comments.getCommentsInFile(sm.getFileID(expr_begin)) != nullptr)
context.Comments.getCommentsInFile(file_id) != nullptr) {
for (const auto [offset, raw_comment] :
*context.Comments.getCommentsInFile(sm.getFileID(expr_begin))) {
const auto comment_end_line = sm.getSpellingLineNumber(
@@ -956,6 +971,7 @@ clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm,
expr_begin_line == comment_end_line + 1)
return raw_comment;
}
}
return {};
}

View File

@@ -299,6 +299,12 @@ consume_type_context(clang::QualType type);
clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm,
const clang::ASTContext &context, const clang::Stmt *stmt);
clang::RawComment *get_declaration_raw_comment(const clang::SourceManager &sm,
const clang::ASTContext &context, const clang::Decl *decl);
clang::RawComment *get_raw_comment(const clang::SourceManager &sm,
const clang::ASTContext &context, const clang::SourceRange &source_range);
/**
* Check if function or method declaration is a C++20 coroutine.
*

View File

@@ -17,10 +17,22 @@
*/
#pragma once
#include "common/model/source_location.h"
#include "util/error.h"
#include "util/util.h"
#include <inja/inja.hpp>
#include <optional>
#include <ostream>
#include <regex>
#include <string>
namespace clanguml::common::generators {
void make_context_source_relative(
inja::json &context, const std::string &prefix);
/**
* @brief Common diagram generator interface
*
@@ -38,6 +50,8 @@ public:
: config_{config}
, model_{model}
{
init_context();
init_env();
}
virtual ~generator() = default;
@@ -68,9 +82,253 @@ public:
*/
const DiagramType &model() const { return model_; }
template <typename E> inja::json element_context(const E &e) const;
std::optional<std::pair<std::string, std::string>> get_link_pattern(
const common::model::source_location &sl) const;
std::optional<std::pair<std::string, std::string>> get_tooltip_pattern(
const common::model::source_location &sl) const;
/**
* @brief Initialize diagram Jinja context
*/
void init_context();
/**
* @brief Update diagram Jinja context
*
* This method updates the diagram context with models properties
* which can be used to render Jinja templates in the diagram (e.g.
* in notes or links)
*/
void update_context() const;
void init_env();
const inja::json &context() const;
inja::Environment &env() const;
protected:
mutable inja::json m_context;
mutable inja::Environment m_env;
private:
ConfigType &config_;
DiagramType &model_;
};
template <typename C, typename D> void generator<C, D>::init_context()
{
const auto &config = generators::generator<C, D>::config();
if (config.git) {
m_context["git"]["branch"] = config.git().branch;
m_context["git"]["revision"] = config.git().revision;
m_context["git"]["commit"] = config.git().commit;
m_context["git"]["toplevel"] = config.git().toplevel;
}
}
template <typename C, typename D> void generator<C, D>::update_context() const
{
m_context["diagram"] = model().context();
}
template <typename C, typename D>
const inja::json &generator<C, D>::context() const
{
return m_context;
}
template <typename C, typename D>
inja::Environment &generator<C, D>::env() const
{
return m_env;
}
template <typename C, typename D> void generator<C, D>::init_env()
{
const auto &model = generators::generator<C, D>::model();
const auto &config = generators::generator<C, D>::config();
//
// Add basic string functions to inja environment
//
// Check if string is empty
m_env.add_callback("empty", 1, [](inja::Arguments &args) {
return args.at(0)->get<std::string>().empty();
});
// Remove spaces from the left of a string
m_env.add_callback("ltrim", 1, [](inja::Arguments &args) {
return util::ltrim(args.at(0)->get<std::string>());
});
// Remove trailing spaces from a string
m_env.add_callback("rtrim", 1, [](inja::Arguments &args) {
return util::rtrim(args.at(0)->get<std::string>());
});
// Remove spaces before and after a string
m_env.add_callback("trim", 1, [](inja::Arguments &args) {
return util::trim(args.at(0)->get<std::string>());
});
// Make a string shorted with a limit to
m_env.add_callback("abbrv", 2, [](inja::Arguments &args) {
return util::abbreviate(
args.at(0)->get<std::string>(), args.at(1)->get<unsigned>());
});
m_env.add_callback("replace", 3, [](inja::Arguments &args) {
std::string result = args[0]->get<std::string>();
std::regex pattern(args[1]->get<std::string>());
return std::regex_replace(result, pattern, args[2]->get<std::string>());
});
m_env.add_callback("split", 2, [](inja::Arguments &args) {
return util::split(
args[0]->get<std::string>(), args[1]->get<std::string>());
});
//
// Add PlantUML specific functions
//
// Return the entire element JSON context based on element name
// e.g.:
// {{ element("clanguml::t00050::A").comment }}
//
m_env.add_callback("element", 1, [&model, &config](inja::Arguments &args) {
inja::json res{};
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (element_opt.has_value())
res = element_opt.value().context();
return res;
});
// Convert C++ entity to PlantUML alias, e.g.
// "note left of {{ alias("A") }}: This is a note"
// Shortcut to:
// {{ element("A").alias }}
//
m_env.add_callback("alias", 1, [&model, &config](inja::Arguments &args) {
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (!element_opt.has_value())
throw clanguml::error::uml_alias_missing(
args[0]->get<std::string>());
return element_opt.value().alias();
});
// Get elements' comment:
// "note left of {{ alias("A") }}: {{ comment("A") }}"
// Shortcut to:
// {{ element("A").comment }}
//
m_env.add_callback("comment", 1, [&model, &config](inja::Arguments &args) {
inja::json res{};
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (!element_opt.has_value())
throw clanguml::error::uml_alias_missing(
args[0]->get<std::string>());
auto comment = element_opt.value().comment();
if (comment.has_value()) {
assert(comment.value().is_object());
res = comment.value();
}
return res;
});
}
template <typename C, typename D>
template <typename E>
inja::json generator<C, D>::element_context(const E &e) const
{
const auto &diagram_context = context();
inja::json ctx;
ctx["element"] = e.context();
#if _MSC_VER
if (ctx.contains("git")) {
#else
if (diagram_context.template contains("git")) {
#endif
ctx["git"] = diagram_context["git"];
}
if (!e.file().empty()) {
std::filesystem::path file{e.file()};
std::string git_relative_path = file.string();
if (!e.file_relative().empty()) {
#if _MSC_VER
if (file.is_absolute() && ctx.contains("git")) {
#else
if (file.is_absolute() &&
diagram_context.template contains("git")) {
#endif
git_relative_path = std::filesystem::relative(
file, diagram_context["git"]["toplevel"])
.string();
ctx["element"]["source"]["path"] =
util::path_to_url(git_relative_path);
}
else {
ctx["element"]["source"]["path"] = e.file();
}
}
else {
git_relative_path = "";
ctx["element"]["source"]["path"] = e.file();
}
ctx["element"]["source"]["full_path"] = file.string();
ctx["element"]["source"]["name"] = file.filename().string();
ctx["element"]["source"]["line"] = e.line();
}
const auto &maybe_comment = e.comment();
if (maybe_comment) {
ctx["element"]["comment"] = maybe_comment.value();
}
return ctx;
}
template <typename C, typename D>
std::optional<std::pair<std::string, std::string>>
generator<C, D>::get_link_pattern(
const common::model::source_location &sl) const
{
if (sl.file_relative().empty()) {
return config().generate_links().get_link_pattern(sl.file());
}
return config().generate_links().get_link_pattern(sl.file_relative());
}
template <typename C, typename D>
std::optional<std::pair<std::string, std::string>>
generator<C, D>::get_tooltip_pattern(
const common::model::source_location &sl) const
{
if (sl.file_relative().empty()) {
return config().generate_links().get_tooltip_pattern(sl.file());
}
return config().generate_links().get_tooltip_pattern(sl.file_relative());
}
} // namespace clanguml::common::generators

View File

@@ -21,6 +21,28 @@
#include "progress_indicator.h"
namespace clanguml::common::generators {
void make_context_source_relative(
inja::json &context, const std::string &prefix)
{
if (!context.contains("element"))
return;
if (!context["element"].contains("source"))
return;
auto &source = context["element"]["source"];
if (source.at("path").empty())
return;
auto path = std::filesystem::path(source.at("path"));
auto prefix_path = std::filesystem::path(prefix);
if (path.is_absolute() && util::is_relative_to(path, prefix_path)) {
source["path"] = relative(path, prefix_path);
return;
}
}
void find_translation_units_for_diagrams(
const std::vector<std::string> &diagram_names,
clanguml::config::config &config,

View File

@@ -22,7 +22,7 @@
#include "class_diagram/generators/plantuml/class_diagram_generator.h"
#include "cli/cli_handler.h"
#include "common/compilation_database.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter_factory.h"
#include "config/config.h"
#include "include_diagram/generators/json/include_diagram_generator.h"
#include "include_diagram/generators/mermaid/include_diagram_generator.h"
@@ -369,7 +369,7 @@ std::unique_ptr<DiagramModel> generate(const common::compilation_database &db,
auto diagram = std::make_unique<DiagramModel>();
diagram->set_name(name);
diagram->set_filter(
std::make_unique<model::diagram_filter>(*diagram, config));
model::diagram_filter_factory::create(*diagram, config));
LOG_DBG("Found translation units for diagram {}: {}", name,
fmt::join(translation_units, ", "));

View File

@@ -18,7 +18,7 @@
#pragma once
#include "common/generators/generator.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter.h"
#include "config/config.h"
#include "util/error.h"
#include "util/util.h"

View File

@@ -78,7 +78,14 @@ std::string indent(const unsigned level)
return std::string(level * kIndentWidth, ' '); // NOLINT
}
std::string render_name(std::string name, bool round_brackets)
std::string render_name(std::string name)
{
util::replace_all(name, "##", "::");
return name;
}
std::string escape_name(std::string name, bool round_brackets)
{
util::replace_all(name, "<", "&lt;");
util::replace_all(name, ">", "&gt;");
@@ -86,7 +93,6 @@ std::string render_name(std::string name, bool round_brackets)
util::replace_all(name, "(", "&lpar;");
util::replace_all(name, ")", "&rpar;");
}
util::replace_all(name, "##", "::");
util::replace_all(name, "{", "&lbrace;");
util::replace_all(name, "}", "&rbrace;");

View File

@@ -18,7 +18,7 @@
#pragma once
#include "common/generators/generator.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter.h"
#include "config/config.h"
#include "util/error.h"
#include "util/util.h"
@@ -44,7 +44,8 @@ std::string to_mermaid(message_t r);
std::string indent(unsigned level);
std::string render_name(std::string name, bool round_brackets = true);
std::string render_name(std::string name);
std::string escape_name(std::string name, bool round_brackets = true);
/**
* @brief Base class for diagram generators
@@ -66,8 +67,6 @@ public:
: clanguml::common::generators::generator<ConfigType, DiagramType>{
config, model}
{
init_context();
init_env();
}
~generator() override = default;
@@ -168,84 +167,11 @@ public:
*/
void print_debug(
const common::model::source_location &e, std::ostream &ostr) const;
/**
* @brief Update diagram Jinja context
*
* This method updates the diagram context with models properties
* which can be used to render Jinja templates in the diagram (e.g.
* in notes or links)
*/
void update_context() const;
protected:
const inja::json &context() const;
inja::Environment &env() const;
template <typename E> inja::json element_context(const E &e) const;
private:
void init_context();
void init_env();
protected:
mutable std::set<std::string> m_generated_aliases;
mutable inja::json m_context;
mutable inja::Environment m_env;
};
template <typename C, typename D>
const inja::json &generator<C, D>::context() const
{
return m_context;
}
template <typename C, typename D>
inja::Environment &generator<C, D>::env() const
{
return m_env;
}
template <typename C, typename D>
template <typename E>
inja::json generator<C, D>::element_context(const E &e) const
{
auto ctx = context();
ctx["element"] = e.context();
if (!e.file().empty()) {
std::filesystem::path file{e.file()};
std::string git_relative_path = file.string();
if (!e.file_relative().empty()) {
#if _MSC_VER
if (file.is_absolute() && ctx.contains("git"))
#else
if (file.is_absolute() && ctx.template contains("git"))
#endif
git_relative_path =
std::filesystem::relative(file, ctx["git"]["toplevel"])
.string();
}
else {
git_relative_path = "";
}
ctx["element"]["source"]["path"] = util::path_to_url(git_relative_path);
ctx["element"]["source"]["full_path"] = file.string();
ctx["element"]["source"]["name"] = file.filename().string();
ctx["element"]["source"]["line"] = e.line();
}
const auto maybe_comment = e.comment();
if (maybe_comment) {
ctx["element"]["comment"] = maybe_comment.value();
}
return ctx;
}
template <typename C, typename D>
void generator<C, D>::generate(std::ostream &ostr) const
{
@@ -258,7 +184,7 @@ void generator<C, D>::generate(std::ostream &ostr) const
"Diagram configuration resulted in empty diagram."};
}
update_context();
generators::generator<C, D>::update_context();
generate_title(ostr);
@@ -277,59 +203,71 @@ template <typename C, typename D>
template <typename E>
void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
{
const auto &config = generators::generator<C, D>::config();
if (e.file().empty())
if (e.file().empty() && e.file_relative().empty())
return;
if (config.generate_links().link.empty() &&
config.generate_links().tooltip.empty())
auto maybe_link_pattern = generators::generator<C, D>::get_link_pattern(e);
if (!maybe_link_pattern)
return;
const auto &[link_prefix, link_pattern] = *maybe_link_pattern;
ostr << indent(1) << "click " << e.alias() << " href \"";
try {
auto ec = generators::generator<C, D>::element_context(e);
common::generators::make_context_source_relative(ec, link_prefix);
std::string link{};
if (!config.generate_links().link.empty()) {
link = env().render(std::string_view{config.generate_links().link},
element_context(e));
if (!link_pattern.empty()) {
link = generators::generator<C, D>::env().render(
std::string_view{link_pattern}, ec);
}
if (link.empty())
link = " ";
ostr << link;
}
catch (const inja::json::parse_error &e) {
LOG_ERROR(
"Failed to parse Jinja template: {}", config.generate_links().link);
LOG_ERROR("Failed to parse Jinja template: {}", link_pattern);
ostr << " ";
}
catch (const inja::json::exception &e) {
LOG_ERROR("Failed to render comment directive: \n{}\n due to: {}",
config.generate_links().link, e.what());
link_pattern, e.what());
ostr << " ";
}
ostr << "\"";
if (!config.generate_links().tooltip.empty()) {
ostr << " \"";
try {
auto tooltip_text =
env().render(std::string_view{config.generate_links().tooltip},
element_context(e));
util::replace_all(tooltip_text, "\"", "&bdquo;");
ostr << tooltip_text;
}
catch (const inja::json::parse_error &e) {
LOG_ERROR("Failed to parse Jinja template: {}",
config.generate_links().link);
ostr << " ";
}
catch (const inja::json::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
config.generate_links().link, e.what());
ostr << " ";
}
auto maybe_tooltip_pattern =
generators::generator<C, D>::get_tooltip_pattern(e);
ostr << "\"";
if (maybe_tooltip_pattern) {
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
if (!tooltip_pattern.empty()) {
ostr << " \"";
try {
auto ec = generators::generator<C, D>::element_context(e);
common::generators::make_context_source_relative(
ec, tooltip_prefix);
auto tooltip_text = generators::generator<C, D>::env().render(
std::string_view{tooltip_pattern}, ec);
util::replace_all(tooltip_text, "\"", "&bdquo;");
ostr << tooltip_text;
}
catch (const inja::json::parse_error &e) {
LOG_ERROR(
"Failed to parse Jinja template: {}", tooltip_pattern);
ostr << " ";
}
catch (const inja::json::exception &e) {
LOG_ERROR(
"Failed to render PlantUML directive: \n{}\n due to: {}",
tooltip_pattern, e.what());
ostr << " ";
}
ostr << "\"";
}
}
ostr << "\n";
}
@@ -347,7 +285,8 @@ void generator<C, D>::generate_mermaid_directives(
for (const auto &d : directives) {
try {
// Render the directive with template engine first
std::string directive{env().render(std::string_view{d}, context())};
std::string directive{generators::generator<C, D>::env().render(
std::string_view{d}, generators::generator<C, D>::context())};
// Now search for alias `@A()` directives in the text
// (this is deprecated)
@@ -452,127 +391,4 @@ std::ostream &operator<<(
g.generate(os);
return os;
}
template <typename C, typename D> void generator<C, D>::init_context()
{
const auto &config = generators::generator<C, D>::config();
if (config.git) {
m_context["git"]["branch"] = config.git().branch;
m_context["git"]["revision"] = config.git().revision;
m_context["git"]["commit"] = config.git().commit;
m_context["git"]["toplevel"] = config.git().toplevel;
}
}
template <typename C, typename D> void generator<C, D>::update_context() const
{
m_context["diagram"] = generators::generator<C, D>::model().context();
}
template <typename C, typename D> void generator<C, D>::init_env()
{
const auto &model = generators::generator<C, D>::model();
const auto &config = generators::generator<C, D>::config();
//
// Add basic string functions to inja environment
//
// Check if string is empty
m_env.add_callback("empty", 1, [](inja::Arguments &args) {
return args.at(0)->get<std::string>().empty();
});
// Remove spaces from the left of a string
m_env.add_callback("ltrim", 1, [](inja::Arguments &args) {
return util::ltrim(args.at(0)->get<std::string>());
});
// Remove trailing spaces from a string
m_env.add_callback("rtrim", 1, [](inja::Arguments &args) {
return util::rtrim(args.at(0)->get<std::string>());
});
// Remove spaces before and after a string
m_env.add_callback("trim", 1, [](inja::Arguments &args) {
return util::trim(args.at(0)->get<std::string>());
});
// Make a string shorted with a limit to
m_env.add_callback("abbrv", 2, [](inja::Arguments &args) {
return util::abbreviate(
args.at(0)->get<std::string>(), args.at(1)->get<unsigned>());
});
m_env.add_callback("replace", 3, [](inja::Arguments &args) {
std::string result = args[0]->get<std::string>();
std::regex pattern(args[1]->get<std::string>());
return std::regex_replace(result, pattern, args[2]->get<std::string>());
});
m_env.add_callback("split", 2, [](inja::Arguments &args) {
return util::split(
args[0]->get<std::string>(), args[1]->get<std::string>());
});
//
// Add MermaidJS specific functions
//
// Return the entire element JSON context based on element name
// e.g.:
// {{ element("clanguml::t00050::A").comment }}
//
m_env.add_callback("element", 1, [&model, &config](inja::Arguments &args) {
inja::json res{};
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (element_opt.has_value())
res = element_opt.value().context();
return res;
});
// Convert C++ entity to MermaidJS alias, e.g.
// "note left of {{ alias("A") }}: This is a note"
// Shortcut to:
// {{ element("A").alias }}
//
m_env.add_callback("alias", 1, [&model, &config](inja::Arguments &args) {
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (!element_opt.has_value())
throw clanguml::error::uml_alias_missing(
args[0]->get<std::string>());
return element_opt.value().alias();
});
// Get elements' comment:
// "note left of {{ alias("A") }}: {{ comment("A") }}"
// Shortcut to:
// {{ element("A").comment }}
//
m_env.add_callback("comment", 1, [&model, &config](inja::Arguments &args) {
inja::json res{};
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (!element_opt.has_value())
throw clanguml::error::uml_alias_missing(
args[0]->get<std::string>());
auto comment = element_opt.value().comment();
if (comment.has_value()) {
assert(comment.value().is_object());
res = comment.value();
}
return res;
});
}
} // namespace clanguml::common::generators::mermaid

View File

@@ -18,10 +18,9 @@
#pragma once
#include "common/generators/generator.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter.h"
#include "common/model/relationship.h"
#include "config/config.h"
#include "util/error.h"
#include "util/util.h"
#include "version.h"
@@ -63,8 +62,6 @@ public:
: clanguml::common::generators::generator<ConfigType, DiagramType>{
config, model}
{
init_context();
init_env();
}
~generator() override = default;
@@ -177,21 +174,6 @@ public:
*/
void print_debug(
const common::model::source_location &e, std::ostream &ostr) const;
/**
* @brief Update diagram Jinja context
*
* This method updates the diagram context with models properties
* which can be used to render Jinja templates in the diagram (e.g.
* in notes or links)
*/
void update_context() const;
protected:
const inja::json &context() const;
inja::Environment &env() const;
template <typename E> inja::json element_context(const E &e) const;
private:
void generate_row_column_hints(std::ostream &ostr,
@@ -200,74 +182,17 @@ private:
void generate_position_hints(std::ostream &ostr,
const std::string &entity_name, const config::layout_hint &hint) const;
void init_context();
void init_env();
protected:
mutable std::set<std::string> m_generated_aliases;
mutable inja::json m_context;
mutable inja::Environment m_env;
};
template <typename C, typename D>
const inja::json &generator<C, D>::context() const
{
return m_context;
}
template <typename C, typename D>
inja::Environment &generator<C, D>::env() const
{
return m_env;
}
template <typename C, typename D>
template <typename E>
inja::json generator<C, D>::element_context(const E &e) const
{
auto ctx = context();
ctx["element"] = e.context();
if (!e.file().empty()) {
std::filesystem::path file{e.file()};
std::string git_relative_path = file.string();
if (!e.file_relative().empty()) {
#if _MSC_VER
if (file.is_absolute() && ctx.contains("git"))
#else
if (file.is_absolute() && ctx.template contains("git"))
#endif
git_relative_path =
std::filesystem::relative(file, ctx["git"]["toplevel"])
.string();
}
else {
git_relative_path = "";
}
ctx["element"]["source"]["path"] = util::path_to_url(git_relative_path);
ctx["element"]["source"]["full_path"] = file.string();
ctx["element"]["source"]["name"] = file.filename().string();
ctx["element"]["source"]["line"] = e.line();
}
const auto &maybe_comment = e.comment();
if (maybe_comment) {
ctx["element"]["comment"] = maybe_comment.value();
}
return ctx;
}
template <typename C, typename D>
void generator<C, D>::generate(std::ostream &ostr) const
{
const auto &config = generators::generator<C, D>::config();
const auto &model = generators::generator<C, D>::model();
update_context();
generators::generator<C, D>::update_context();
if (!config.allow_empty_diagrams() && model.is_empty() &&
config.puml().before.empty() && config.puml().after.empty()) {
@@ -413,7 +338,8 @@ void generator<C, D>::generate_plantuml_directives(
for (const auto &d : directives) {
try {
// Render the directive with template engine first
std::string directive{env().render(std::string_view{d}, context())};
std::string directive{generators::generator<C, D>::env().render(
std::string_view{d}, generators::generator<C, D>::context())};
// Now search for alias `@A()` directives in the text
// (this is deprecated)
@@ -519,45 +445,59 @@ template <typename C, typename D>
template <typename E>
void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
{
const auto &config = generators::generator<C, D>::config();
if (e.file_relative().empty())
if (e.file().empty() && e.file_relative().empty())
return;
auto maybe_link_pattern = generators::generator<C, D>::get_link_pattern(e);
if (!maybe_link_pattern) {
return;
}
const auto &[link_prefix, link_pattern] = *maybe_link_pattern;
ostr << " [[";
try {
if (!config.generate_links().link.empty()) {
ostr << env().render(std::string_view{config.generate_links().link},
element_context(e));
if (!link_pattern.empty()) {
auto ec = generators::generator<C, D>::element_context(e);
common::generators::make_context_source_relative(ec, link_prefix);
ostr << generators::generator<C, D>::env().render(
std::string_view{link_pattern}, ec);
}
}
catch (const inja::json::parse_error &e) {
LOG_ERROR(
"Failed to parse Jinja template: {}", config.generate_links().link);
LOG_ERROR("Failed to parse Jinja template: {}", link_pattern);
}
catch (const inja::json::exception &e) {
catch (const std::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
config.generate_links().link, e.what());
link_pattern, e.what());
}
ostr << "{";
try {
if (!config.generate_links().tooltip.empty()) {
ostr << env().render(
std::string_view{config.generate_links().tooltip},
element_context(e));
auto maybe_tooltip_pattern =
generators::generator<C, D>::get_tooltip_pattern(e);
if (maybe_tooltip_pattern) {
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
ostr << "{";
try {
auto ec = generators::generator<C, D>::element_context(e);
common::generators::make_context_source_relative(
ec, tooltip_prefix);
if (!tooltip_pattern.empty()) {
ostr << generators::generator<C, D>::env().render(
std::string_view{tooltip_pattern}, ec);
}
}
catch (const inja::json::parse_error &e) {
LOG_ERROR("Failed to parse Jinja template: {}", tooltip_pattern);
}
catch (const std::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
tooltip_pattern, e.what());
}
ostr << "}";
}
catch (const inja::json::parse_error &e) {
LOG_ERROR(
"Failed to parse Jinja template: {}", config.generate_links().link);
}
catch (const inja::json::exception &e) {
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
config.generate_links().link, e.what());
}
ostr << "}";
ostr << "]]";
}
@@ -579,126 +519,4 @@ std::ostream &operator<<(
return os;
}
template <typename C, typename D> void generator<C, D>::init_context()
{
const auto &config = generators::generator<C, D>::config();
if (config.git) {
m_context["git"]["branch"] = config.git().branch;
m_context["git"]["revision"] = config.git().revision;
m_context["git"]["commit"] = config.git().commit;
m_context["git"]["toplevel"] = config.git().toplevel;
}
}
template <typename C, typename D> void generator<C, D>::update_context() const
{
m_context["diagram"] = generators::generator<C, D>::model().context();
}
template <typename C, typename D> void generator<C, D>::init_env()
{
const auto &model = generators::generator<C, D>::model();
const auto &config = generators::generator<C, D>::config();
//
// Add basic string functions to inja environment
//
// Check if string is empty
m_env.add_callback("empty", 1, [](inja::Arguments &args) {
return args.at(0)->get<std::string>().empty();
});
// Remove spaces from the left of a string
m_env.add_callback("ltrim", 1, [](inja::Arguments &args) {
return util::ltrim(args.at(0)->get<std::string>());
});
// Remove trailing spaces from a string
m_env.add_callback("rtrim", 1, [](inja::Arguments &args) {
return util::rtrim(args.at(0)->get<std::string>());
});
// Remove spaces before and after a string
m_env.add_callback("trim", 1, [](inja::Arguments &args) {
return util::trim(args.at(0)->get<std::string>());
});
// Make a string shorted with a limit to
m_env.add_callback("abbrv", 2, [](inja::Arguments &args) {
return util::abbreviate(
args.at(0)->get<std::string>(), args.at(1)->get<unsigned>());
});
m_env.add_callback("replace", 3, [](inja::Arguments &args) {
std::string result = args[0]->get<std::string>();
std::regex pattern(args[1]->get<std::string>());
return std::regex_replace(result, pattern, args[2]->get<std::string>());
});
m_env.add_callback("split", 2, [](inja::Arguments &args) {
return util::split(
args[0]->get<std::string>(), args[1]->get<std::string>());
});
//
// Add PlantUML specific functions
//
// Return the entire element JSON context based on element name
// e.g.:
// {{ element("clanguml::t00050::A").comment }}
//
m_env.add_callback("element", 1, [&model, &config](inja::Arguments &args) {
inja::json res{};
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (element_opt.has_value())
res = element_opt.value().context();
return res;
});
// Convert C++ entity to PlantUML alias, e.g.
// "note left of {{ alias("A") }}: This is a note"
// Shortcut to:
// {{ element("A").alias }}
//
m_env.add_callback("alias", 1, [&model, &config](inja::Arguments &args) {
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (!element_opt.has_value())
throw clanguml::error::uml_alias_missing(
args[0]->get<std::string>());
return element_opt.value().alias();
});
// Get elements' comment:
// "note left of {{ alias("A") }}: {{ comment("A") }}"
// Shortcut to:
// {{ element("A").comment }}
//
m_env.add_callback("comment", 1, [&model, &config](inja::Arguments &args) {
inja::json res{};
auto element_opt = model.get_with_namespace(
args[0]->get<std::string>(), config.using_namespace());
if (!element_opt.has_value())
throw clanguml::error::uml_alias_missing(
args[0]->get<std::string>());
auto comment = element_opt.value().comment();
if (comment.has_value()) {
assert(comment.value().is_object());
res = comment.value();
}
return res;
});
}
} // namespace clanguml::common::generators::plantuml

View File

@@ -18,7 +18,7 @@
#include "diagram.h"
#include "diagram_filter.h"
#include "filters/diagram_filter.h"
#include "namespace.h"
namespace clanguml::common::model {
@@ -59,19 +59,35 @@ void diagram::set_complete(bool complete) { complete_ = complete; }
bool diagram::complete() const { return complete_; }
void diagram::finalize() { }
void diagram::finalize()
{
// Remove elements that do not match the filter
apply_filter();
filtered_ = true;
}
bool diagram::should_include(const element &e) const
{
if (filtered_)
return true;
if (filter_.get() == nullptr)
return true;
if (!complete()) {
return filter_->should_include(
dynamic_cast<const source_location &>(e));
}
return filter_->should_include(e) &&
filter_->should_include(dynamic_cast<const source_location &>(e));
}
bool diagram::should_include(const namespace_ &ns) const
{
if (filtered_)
return true;
if (filter_.get() == nullptr)
return true;

View File

@@ -171,10 +171,13 @@ public:
*/
virtual bool is_empty() const = 0;
virtual void apply_filter() { }
private:
std::string name_;
std::unique_ptr<diagram_filter> filter_;
bool complete_{false};
bool filtered_{false};
};
template <typename DiagramT> bool check_diagram_type(diagram_t t);

View File

@@ -18,6 +18,7 @@
#include "diagram_element.h"
#include "common/model/filters/diagram_filter.h"
#include "util/util.h"
#include <ostream>
@@ -102,6 +103,19 @@ bool diagram_element::complete() const { return complete_; }
void diagram_element::complete(bool completed) { complete_ = completed; }
void diagram_element::apply_filter(
const diagram_filter &filter, const std::set<eid_t> &removed)
{
common::model::apply_filter(relationships(), filter);
auto &rels = relationships();
rels.erase(std::remove_if(std::begin(rels), std::end(rels),
[&removed](auto &&r) {
return removed.count(r.destination()) > 0;
}),
std::end(rels));
}
bool operator==(const diagram_element &l, const diagram_element &r)
{
return l.id() == r.id();

View File

@@ -26,11 +26,14 @@
#include <atomic>
#include <exception>
#include <set>
#include <string>
#include <vector>
namespace clanguml::common::model {
class diagram_filter;
/**
* @brief Base class for standalone diagram elements.
*
@@ -184,6 +187,9 @@ public:
*/
void complete(bool completed);
virtual void apply_filter(
const diagram_filter &filter, const std::set<eid_t> &removed);
private:
eid_t id_{};
std::optional<eid_t> parent_element_id_{};

View File

@@ -78,6 +78,15 @@ public:
*/
bool is_empty() const { return elements_.empty(); }
void remove(const std::set<eid_t> &element_ids)
{
elements_.erase(std::remove_if(elements_.begin(), elements_.end(),
[&element_ids](auto &&e) {
return element_ids.count(e.get().id()) > 0;
}),
elements_.end());
}
private:
reference_vector<T> elements_;
};

View File

@@ -99,6 +99,12 @@ tvl::value_t filter_visitor::match(
return {};
}
tvl::value_t filter_visitor::match(
const diagram &d, const common::model::relationship &r) const
{
return match(d, r.type());
}
tvl::value_t filter_visitor::match(
const diagram & /*d*/, const common::model::relationship_t & /*r*/) const
{
@@ -159,6 +165,10 @@ bool filter_visitor::is_exclusive() const
filter_t filter_visitor::type() const { return type_; }
filter_mode_t filter_visitor::mode() const { return mode_; }
void filter_visitor::set_mode(filter_mode_t mode) { mode_ = mode; }
anyof_filter::anyof_filter(
filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters)
: filter_visitor{type}
@@ -169,22 +179,116 @@ anyof_filter::anyof_filter(
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::element &e) const
{
return tvl::any_of(filters_.begin(), filters_.end(),
[&d, &e](const auto &f) { return f->match(d, e); });
return match_anyof(d, e);
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::relationship_t &r) const
{
return match_anyof(d, r);
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::access_t &a) const
{
return match_anyof(d, a);
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::namespace_ &ns) const
{
return match_anyof(d, ns);
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::source_file &f) const
{
return match_anyof(d, f);
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::source_location &f) const
{
return match_anyof(d, f);
}
tvl::value_t anyof_filter::match(
const diagram &d, const class_diagram::model::class_method &m) const
{
return match_anyof(d, m);
}
tvl::value_t anyof_filter::match(
const diagram &d, const class_diagram::model::class_member &m) const
{
return match_anyof(d, m);
}
tvl::value_t anyof_filter::match(
const diagram &d, const sequence_diagram::model::participant &p) const
{
return tvl::any_of(filters_.begin(), filters_.end(),
[&d, &p](const auto &f) { return f->match(d, p); });
return match_anyof(d, p);
}
tvl::value_t anyof_filter::match(
const diagram &d, const common::model::source_file &e) const
allof_filter::allof_filter(
filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters)
: filter_visitor{type}
, filters_{std::move(filters)}
{
return tvl::any_of(filters_.begin(), filters_.end(),
[&d, &e](const auto &f) { return f->match(d, e); });
}
tvl::value_t allof_filter::match(
const diagram &d, const common::model::element &e) const
{
return match_allof(d, e);
}
tvl::value_t allof_filter::match(
const diagram &d, const common::model::relationship_t &r) const
{
return match_allof(d, r);
}
tvl::value_t allof_filter::match(
const diagram &d, const common::model::access_t &a) const
{
return match_allof(d, a);
}
tvl::value_t allof_filter::match(
const diagram &d, const common::model::namespace_ &ns) const
{
return match_allof(d, ns);
}
tvl::value_t allof_filter::match(
const diagram &d, const common::model::source_file &f) const
{
return match_allof(d, f);
}
tvl::value_t allof_filter::match(
const diagram &d, const common::model::source_location &f) const
{
return match_allof(d, f);
}
tvl::value_t allof_filter::match(
const diagram &d, const class_diagram::model::class_method &m) const
{
return match_allof(d, m);
}
tvl::value_t allof_filter::match(
const diagram &d, const class_diagram::model::class_member &m) const
{
return match_allof(d, m);
}
tvl::value_t allof_filter::match(
const diagram &d, const sequence_diagram::model::participant &p) const
{
return match_allof(d, p);
}
namespace_filter::namespace_filter(
@@ -285,6 +389,12 @@ tvl::value_t namespace_filter::match(const diagram &d, const element &e) const
return result;
}
tvl::value_t namespace_filter::match(
const diagram &d, const sequence_diagram::model::participant &p) const
{
return match(d, dynamic_cast<const element &>(p));
}
modules_filter::modules_filter(
filter_t type, std::vector<common::string_or_regex> modules)
: filter_visitor{type}
@@ -845,8 +955,8 @@ bool context_filter::is_outward(relationship_t r) const
return r != relationship_t::kAssociation;
}
paths_filter::paths_filter(filter_t type, const std::filesystem::path &root,
const std::vector<std::string> &p)
paths_filter::paths_filter(filter_t type, const std::vector<std::string> &p,
const std::filesystem::path &root)
: filter_visitor{type}
, root_{root}
{
@@ -899,7 +1009,7 @@ tvl::value_t paths_filter::match(
return {};
}
// Matching source paths doesn't make sens if they are not absolute
// Matching source paths doesn't make sense if they are not absolute
if (!p.is_absolute()) {
return {};
}
@@ -972,11 +1082,19 @@ tvl::value_t class_member_filter::match(
return access_filter_->match(d, m.access());
}
diagram_filter::diagram_filter(
const common::model::diagram &d, const config::diagram &c)
diagram_filter::diagram_filter(const common::model::diagram &d,
const config::diagram & /*c*/, private_constructor_tag_t /*unused*/)
: diagram_{d}
{
init_filters(c);
}
void diagram_filter::add_filter(
filter_t filter_type, std::unique_ptr<filter_visitor> fv)
{
if (filter_type == filter_t::kInclusive)
add_inclusive_filter(std::move(fv));
else
add_exclusive_filter(std::move(fv));
}
void diagram_filter::add_inclusive_filter(std::unique_ptr<filter_visitor> fv)
@@ -1003,254 +1121,6 @@ bool diagram_filter::should_include(
return false;
}
void diagram_filter::init_filters(const config::diagram &c)
{
using specializations_filter_t =
edge_traversal_filter<class_diagram::model::diagram,
class_diagram::model::class_, common::string_or_regex>;
using class_dependants_filter_t =
edge_traversal_filter<class_diagram::model::diagram,
class_diagram::model::class_, common::string_or_regex>;
using class_dependencies_filter_t =
edge_traversal_filter<class_diagram::model::diagram,
class_diagram::model::class_, common::string_or_regex>;
using package_dependants_filter_t =
edge_traversal_filter<package_diagram::model::diagram,
common::model::package, common::string_or_regex>;
using package_dependencies_filter_t =
edge_traversal_filter<package_diagram::model::diagram,
common::model::package, common::string_or_regex>;
using source_file_dependency_filter_t =
edge_traversal_filter<include_diagram::model::diagram,
common::model::source_file, std::string,
common::model::source_file>;
// Process inclusive filters
if (c.include) {
add_inclusive_filter(std::make_unique<namespace_filter>(
filter_t::kInclusive, c.include().namespaces));
add_inclusive_filter(std::make_unique<modules_filter>(
filter_t::kInclusive, c.include().modules));
add_inclusive_filter(std::make_unique<module_access_filter>(
filter_t::kInclusive, c.include().module_access));
add_inclusive_filter(std::make_unique<relationship_filter>(
filter_t::kInclusive, c.include().relationships));
add_inclusive_filter(std::make_unique<access_filter>(
filter_t::kInclusive, c.include().access));
add_inclusive_filter(std::make_unique<paths_filter>(
filter_t::kInclusive, c.root_directory(), c.include().paths));
add_inclusive_filter(
std::make_unique<class_method_filter>(filter_t::kInclusive,
std::make_unique<access_filter>(
filter_t::kInclusive, c.include().access),
std::make_unique<method_type_filter>(
filter_t::kInclusive, c.include().method_types)));
add_inclusive_filter(
std::make_unique<class_member_filter>(filter_t::kInclusive,
std::make_unique<access_filter>(
filter_t::kInclusive, c.include().access)));
// Include any of these matches even if one them does not match
std::vector<std::unique_ptr<filter_visitor>> element_filters;
element_filters.emplace_back(std::make_unique<element_filter>(
filter_t::kInclusive, c.include().elements));
element_filters.emplace_back(std::make_unique<element_type_filter>(
filter_t::kInclusive, c.include().element_types));
if (c.type() == diagram_t::kClass) {
element_filters.emplace_back(std::make_unique<subclass_filter>(
filter_t::kInclusive, c.include().subclasses));
element_filters.emplace_back(std::make_unique<parents_filter>(
filter_t::kInclusive, c.include().parents));
element_filters.emplace_back(
std::make_unique<specializations_filter_t>(filter_t::kInclusive,
relationship_t::kInstantiation,
c.include().specializations));
element_filters.emplace_back(
std::make_unique<class_dependants_filter_t>(
filter_t::kInclusive, relationship_t::kDependency,
c.include().dependants));
element_filters.emplace_back(
std::make_unique<class_dependencies_filter_t>(
filter_t::kInclusive, relationship_t::kDependency,
c.include().dependencies, true));
}
else if (c.type() == diagram_t::kSequence) {
element_filters.emplace_back(std::make_unique<callee_filter>(
filter_t::kInclusive, c.include().callee_types));
}
else if (c.type() == diagram_t::kPackage) {
element_filters.emplace_back(
std::make_unique<package_dependants_filter_t>(
filter_t::kInclusive, relationship_t::kDependency,
c.include().dependants));
element_filters.emplace_back(
std::make_unique<package_dependencies_filter_t>(
filter_t::kInclusive, relationship_t::kDependency,
c.include().dependencies, true));
}
else if (c.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> dependencies;
for (auto &&path : c.include().dependants) {
if (auto p = path.get<std::string>(); p.has_value()) {
const std::filesystem::path dep_path{*p};
dependants.emplace_back(
dep_path.lexically_normal().string());
}
}
for (auto &&path : c.include().dependencies) {
if (auto p = path.get<std::string>(); p.has_value()) {
const std::filesystem::path dep_path{*p};
dependencies.emplace_back(
dep_path.lexically_normal().string());
}
}
element_filters.emplace_back(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kInclusive, relationship_t::kAssociation,
dependants, false));
element_filters.emplace_back(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kInclusive, relationship_t::kAssociation,
dependencies, true));
}
element_filters.emplace_back(std::make_unique<context_filter>(
filter_t::kInclusive, c.include().context));
add_inclusive_filter(std::make_unique<anyof_filter>(
filter_t::kInclusive, std::move(element_filters)));
}
// Process exclusive filters
if (c.exclude) {
add_exclusive_filter(std::make_unique<namespace_filter>(
filter_t::kExclusive, c.exclude().namespaces));
add_exclusive_filter(std::make_unique<modules_filter>(
filter_t::kExclusive, c.exclude().modules));
add_exclusive_filter(std::make_unique<module_access_filter>(
filter_t::kExclusive, c.exclude().module_access));
add_exclusive_filter(std::make_unique<paths_filter>(
filter_t::kExclusive, c.root_directory(), c.exclude().paths));
add_exclusive_filter(std::make_unique<element_filter>(
filter_t::kExclusive, c.exclude().elements));
add_exclusive_filter(std::make_unique<element_type_filter>(
filter_t::kExclusive, c.exclude().element_types));
add_exclusive_filter(std::make_unique<relationship_filter>(
filter_t::kExclusive, c.exclude().relationships));
add_exclusive_filter(std::make_unique<access_filter>(
filter_t::kExclusive, c.exclude().access));
add_exclusive_filter(
std::make_unique<class_method_filter>(filter_t::kExclusive,
std::make_unique<access_filter>(
filter_t::kExclusive, c.exclude().access),
std::make_unique<method_type_filter>(
filter_t::kExclusive, c.exclude().method_types)));
add_exclusive_filter(
std::make_unique<class_member_filter>(filter_t::kExclusive,
std::make_unique<access_filter>(
filter_t::kExclusive, c.exclude().access)));
add_exclusive_filter(std::make_unique<subclass_filter>(
filter_t::kExclusive, c.exclude().subclasses));
add_exclusive_filter(std::make_unique<parents_filter>(
filter_t::kExclusive, c.exclude().parents));
add_exclusive_filter(
std::make_unique<specializations_filter_t>(filter_t::kExclusive,
relationship_t::kInstantiation, c.exclude().specializations));
if (c.type() == diagram_t::kClass) {
add_exclusive_filter(std::make_unique<class_dependants_filter_t>(
filter_t::kExclusive, relationship_t::kDependency,
c.exclude().dependants));
add_exclusive_filter(std::make_unique<class_dependencies_filter_t>(
filter_t::kExclusive, relationship_t::kDependency,
c.exclude().dependencies, true));
}
else if (c.type() == diagram_t::kSequence) {
add_exclusive_filter(std::make_unique<callee_filter>(
filter_t::kExclusive, c.exclude().callee_types));
}
else if (c.type() == diagram_t::kPackage) {
add_exclusive_filter(
std::make_unique<package_dependencies_filter_t>(
filter_t::kExclusive, relationship_t::kDependency,
c.exclude().dependencies, true));
add_exclusive_filter(std::make_unique<package_dependants_filter_t>(
filter_t::kExclusive, relationship_t::kDependency,
c.exclude().dependants));
}
else if (c.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> dependencies;
for (auto &&path : c.exclude().dependants) {
if (auto p = path.get<std::string>(); p.has_value()) {
std::filesystem::path dep_path{*p};
dependants.emplace_back(
dep_path.lexically_normal().string());
}
}
for (auto &&path : c.exclude().dependencies) {
if (auto p = path.get<std::string>(); p.has_value()) {
std::filesystem::path dep_path{*p};
dependencies.emplace_back(
dep_path.lexically_normal().string());
}
}
add_exclusive_filter(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kExclusive, relationship_t::kAssociation,
dependants, false));
add_exclusive_filter(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kExclusive, relationship_t::kAssociation,
dependencies, true));
}
add_exclusive_filter(std::make_unique<context_filter>(
filter_t::kExclusive, c.exclude().context));
}
}
template <>
bool diagram_filter::should_include<std::string>(const std::string &name) const
{

View File

@@ -1,5 +1,5 @@
/**
* @file src/common/model/diagram_filter.h
* @file src/common/model/filters/diagram_filter.h
*
* Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
*
@@ -25,19 +25,21 @@
#include "common/model/element.h"
#include "common/model/enums.h"
#include "common/model/namespace.h"
#include "common/model/source_file.h"
#include "common/model/tvl.h"
#include "config/config.h"
#include "diagram.h"
#include "include_diagram/model/diagram.h"
#include "sequence_diagram/model/participant.h"
#include "source_file.h"
#include "tvl.h"
#include <filesystem>
#include <utility>
namespace clanguml::common::model {
class diagram_filter_factory;
using clanguml::common::eid_t;
using clanguml::config::filter_mode_t;
/**
* Diagram filters can be add in 2 modes:
@@ -84,6 +86,9 @@ public:
virtual tvl::value_t match(
const diagram &d, const common::model::element &e) const;
virtual tvl::value_t match(
const diagram &d, const common::model::relationship &r) const;
virtual tvl::value_t match(
const diagram &d, const common::model::relationship_t &r) const;
@@ -112,9 +117,12 @@ public:
bool is_exclusive() const;
filter_t type() const;
filter_mode_t mode() const;
void set_mode(filter_mode_t mode);
private:
filter_t type_;
filter_mode_t mode_{filter_mode_t::basic};
};
struct anyof_filter : public filter_visitor {
@@ -127,12 +135,86 @@ struct anyof_filter : public filter_visitor {
const diagram &d, const common::model::element &e) const override;
tvl::value_t match(const diagram &d,
const sequence_diagram::model::participant &p) const override;
const common::model::relationship_t &r) const override;
tvl::value_t match(
const diagram &d, const common::model::source_file &e) const override;
const diagram &d, const common::model::access_t &a) const override;
tvl::value_t match(
const diagram &d, const common::model::namespace_ &ns) const override;
tvl::value_t match(
const diagram &d, const common::model::source_file &f) const override;
tvl::value_t match(const diagram &d,
const common::model::source_location &f) const override;
tvl::value_t match(const diagram &d,
const class_diagram::model::class_method &m) const override;
tvl::value_t match(const diagram &d,
const class_diagram::model::class_member &m) const override;
tvl::value_t match(const diagram &d,
const sequence_diagram::model::participant &p) const override;
private:
template <typename E>
tvl::value_t match_anyof(const diagram &d, const E &element) const
{
auto result = tvl::any_of(filters_.begin(), filters_.end(),
[&d, &element](const auto &f) { return f->match(d, element); });
if (mode() == filter_mode_t::advanced && !d.complete())
return type() == filter_t::kInclusive;
return result;
}
std::vector<std::unique_ptr<filter_visitor>> filters_;
};
struct allof_filter : public filter_visitor {
allof_filter(
filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters);
~allof_filter() override = default;
tvl::value_t match(
const diagram &d, const common::model::element &e) const override;
tvl::value_t match(const diagram &d,
const common::model::relationship_t &r) const override;
tvl::value_t match(
const diagram &d, const common::model::access_t &a) const override;
tvl::value_t match(
const diagram &d, const common::model::namespace_ &ns) const override;
tvl::value_t match(
const diagram &d, const common::model::source_file &f) const override;
tvl::value_t match(const diagram &d,
const common::model::source_location &f) const override;
tvl::value_t match(const diagram &d,
const class_diagram::model::class_method &m) const override;
tvl::value_t match(const diagram &d,
const class_diagram::model::class_member &m) const override;
tvl::value_t match(const diagram &d,
const sequence_diagram::model::participant &p) const override;
private:
template <typename E>
tvl::value_t match_allof(const diagram &d, const E &element) const
{
return tvl::all_of(filters_.begin(), filters_.end(),
[&d, &element](const auto &f) { return f->match(d, element); });
}
std::vector<std::unique_ptr<filter_visitor>> filters_;
};
@@ -150,6 +232,9 @@ struct namespace_filter : public filter_visitor {
tvl::value_t match(const diagram &d, const element &e) const override;
tvl::value_t match(const diagram &d,
const sequence_diagram::model::participant &p) const override;
private:
std::vector<common::namespace_or_regex> namespaces_;
};
@@ -280,8 +365,8 @@ template <typename DiagramT, typename ElementT,
typename ConfigEntryT = std::string,
typename MatchOverrideT = common::model::element>
struct edge_traversal_filter : public filter_visitor {
edge_traversal_filter(filter_t type, relationship_t relationship,
std::vector<ConfigEntryT> roots, bool forward = false)
edge_traversal_filter(filter_t type, std::vector<ConfigEntryT> roots,
relationship_t relationship, bool forward = false)
: filter_visitor{type}
, roots_{std::move(roots)}
, relationship_{relationship}
@@ -612,8 +697,8 @@ private:
* a specified file paths.
*/
struct paths_filter : public filter_visitor {
paths_filter(filter_t type, const std::filesystem::path &root,
const std::vector<std::string> &p);
paths_filter(filter_t type, const std::vector<std::string> &p,
const std::filesystem::path &root);
~paths_filter() override = default;
@@ -660,6 +745,8 @@ private:
std::unique_ptr<access_filter> access_filter_;
};
class diagram_filter_factory;
/**
* @brief Composite of all diagrams filters.
*
@@ -671,8 +758,14 @@ private:
* @see clanguml::common::model::filter_visitor
*/
class diagram_filter {
private:
struct private_constructor_tag_t { };
public:
diagram_filter(const common::model::diagram &d, const config::diagram &c);
diagram_filter(const common::model::diagram &d, const config::diagram &c,
private_constructor_tag_t unused);
void add_filter(filter_t filter_type, std::unique_ptr<filter_visitor> fv);
/**
* Add inclusive filter.
@@ -725,16 +818,9 @@ public:
return static_cast<bool>(tvl::is_undefined(inc) || tvl::is_true(inc));
}
private:
/**
* @brief Initialize filters.
*
* Some filters require initialization.
*
* @param c Diagram config.
*/
void init_filters(const config::diagram &c);
friend class diagram_filter_factory;
private:
/*! List of inclusive filters */
std::vector<std::unique_ptr<filter_visitor>> inclusive_;
@@ -745,6 +831,27 @@ private:
const common::model::diagram &diagram_;
};
template <typename Collection>
void apply_filter(Collection &col, const diagram_filter &filter)
{
col.erase(std::remove_if(col.begin(), col.end(),
[&filter](auto &&element) {
return !filter.should_include(element);
}),
col.end());
}
template <typename T>
void apply_filter(
std::vector<std::reference_wrapper<T>> &col, const diagram_filter &filter)
{
col.erase(std::remove_if(col.begin(), col.end(),
[&filter](auto &&element) {
return !filter.should_include(element.get());
}),
col.end());
}
template <>
bool diagram_filter::should_include<std::string>(const std::string &name) const;
} // namespace clanguml::common::model

View File

@@ -0,0 +1,366 @@
/**
* @file src/common/model/filters/diagram_filter_factory.cc
*
* Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "diagram_filter_factory.h"
namespace clanguml::common::model {
void basic_diagram_filter_initializer::initialize()
{
// Process inclusive filters
if (diagram_config.include) {
df.add_inclusive_filter(std::make_unique<namespace_filter>(
filter_t::kInclusive, diagram_config.include().namespaces));
df.add_inclusive_filter(std::make_unique<modules_filter>(
filter_t::kInclusive, diagram_config.include().modules));
df.add_inclusive_filter(std::make_unique<module_access_filter>(
filter_t::kInclusive, diagram_config.include().module_access));
df.add_inclusive_filter(std::make_unique<relationship_filter>(
filter_t::kInclusive, diagram_config.include().relationships));
df.add_inclusive_filter(std::make_unique<access_filter>(
filter_t::kInclusive, diagram_config.include().access));
df.add_inclusive_filter(std::make_unique<paths_filter>(
filter_t::kInclusive, diagram_config.include().paths,
diagram_config.root_directory()));
df.add_inclusive_filter(
std::make_unique<class_method_filter>(filter_t::kInclusive,
std::make_unique<access_filter>(
filter_t::kInclusive, diagram_config.include().access),
std::make_unique<method_type_filter>(filter_t::kInclusive,
diagram_config.include().method_types)));
df.add_inclusive_filter(
std::make_unique<class_member_filter>(filter_t::kInclusive,
std::make_unique<access_filter>(
filter_t::kInclusive, diagram_config.include().access)));
// Include any of these matches even if one them does not match
std::vector<std::unique_ptr<filter_visitor>> element_filters;
element_filters.emplace_back(std::make_unique<element_filter>(
filter_t::kInclusive, diagram_config.include().elements));
element_filters.emplace_back(std::make_unique<element_type_filter>(
filter_t::kInclusive, diagram_config.include().element_types));
if (diagram_config.type() == diagram_t::kClass) {
element_filters.emplace_back(std::make_unique<subclass_filter>(
filter_t::kInclusive, diagram_config.include().subclasses));
element_filters.emplace_back(std::make_unique<parents_filter>(
filter_t::kInclusive, diagram_config.include().parents));
element_filters.emplace_back(
std::make_unique<specializations_filter_t>(filter_t::kInclusive,
diagram_config.include().specializations,
relationship_t::kInstantiation));
element_filters.emplace_back(
std::make_unique<class_dependants_filter_t>(
filter_t::kInclusive, diagram_config.include().dependants,
relationship_t::kDependency));
element_filters.emplace_back(
std::make_unique<class_dependencies_filter_t>(
filter_t::kInclusive, diagram_config.include().dependencies,
relationship_t::kDependency, true));
}
else if (diagram_config.type() == diagram_t::kSequence) {
element_filters.emplace_back(std::make_unique<callee_filter>(
filter_t::kInclusive, diagram_config.include().callee_types));
}
else if (diagram_config.type() == diagram_t::kPackage) {
element_filters.emplace_back(
std::make_unique<package_dependants_filter_t>(
filter_t::kInclusive, diagram_config.include().dependants,
relationship_t::kDependency));
element_filters.emplace_back(
std::make_unique<package_dependencies_filter_t>(
filter_t::kInclusive, diagram_config.include().dependencies,
relationship_t::kDependency, true));
}
else if (diagram_config.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> dependencies;
for (auto &&path : diagram_config.include().dependants) {
if (auto p = path.get<std::string>(); p.has_value()) {
const std::filesystem::path dep_path{*p};
dependants.emplace_back(
dep_path.lexically_normal().string());
}
}
for (auto &&path : diagram_config.include().dependencies) {
if (auto p = path.get<std::string>(); p.has_value()) {
const std::filesystem::path dep_path{*p};
dependencies.emplace_back(
dep_path.lexically_normal().string());
}
}
element_filters.emplace_back(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kInclusive, dependants,
relationship_t::kAssociation, false));
element_filters.emplace_back(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kInclusive, dependencies,
relationship_t::kAssociation, true));
}
element_filters.emplace_back(std::make_unique<context_filter>(
filter_t::kInclusive, diagram_config.include().context));
df.add_inclusive_filter(std::make_unique<anyof_filter>(
filter_t::kInclusive, std::move(element_filters)));
}
// Process exclusive filters
if (diagram_config.exclude) {
df.add_exclusive_filter(std::make_unique<namespace_filter>(
filter_t::kExclusive, diagram_config.exclude().namespaces));
df.add_exclusive_filter(std::make_unique<modules_filter>(
filter_t::kExclusive, diagram_config.exclude().modules));
df.add_exclusive_filter(std::make_unique<module_access_filter>(
filter_t::kExclusive, diagram_config.exclude().module_access));
df.add_exclusive_filter(std::make_unique<paths_filter>(
filter_t::kExclusive, diagram_config.exclude().paths,
diagram_config.root_directory()));
df.add_exclusive_filter(std::make_unique<element_filter>(
filter_t::kExclusive, diagram_config.exclude().elements));
df.add_exclusive_filter(std::make_unique<element_type_filter>(
filter_t::kExclusive, diagram_config.exclude().element_types));
df.add_exclusive_filter(std::make_unique<relationship_filter>(
filter_t::kExclusive, diagram_config.exclude().relationships));
df.add_exclusive_filter(std::make_unique<access_filter>(
filter_t::kExclusive, diagram_config.exclude().access));
df.add_exclusive_filter(
std::make_unique<class_method_filter>(filter_t::kExclusive,
std::make_unique<access_filter>(
filter_t::kExclusive, diagram_config.exclude().access),
std::make_unique<method_type_filter>(filter_t::kExclusive,
diagram_config.exclude().method_types)));
df.add_exclusive_filter(
std::make_unique<class_member_filter>(filter_t::kExclusive,
std::make_unique<access_filter>(
filter_t::kExclusive, diagram_config.exclude().access)));
df.add_exclusive_filter(std::make_unique<subclass_filter>(
filter_t::kExclusive, diagram_config.exclude().subclasses));
df.add_exclusive_filter(std::make_unique<parents_filter>(
filter_t::kExclusive, diagram_config.exclude().parents));
df.add_exclusive_filter(std::make_unique<specializations_filter_t>(
filter_t::kExclusive, diagram_config.exclude().specializations,
relationship_t::kInstantiation));
if (diagram_config.type() == diagram_t::kClass) {
df.add_exclusive_filter(std::make_unique<class_dependants_filter_t>(
filter_t::kExclusive, diagram_config.exclude().dependants,
relationship_t::kDependency));
df.add_exclusive_filter(
std::make_unique<class_dependencies_filter_t>(
filter_t::kExclusive, diagram_config.exclude().dependencies,
relationship_t::kDependency, true));
}
else if (diagram_config.type() == diagram_t::kSequence) {
df.add_exclusive_filter(std::make_unique<callee_filter>(
filter_t::kExclusive, diagram_config.exclude().callee_types));
}
else if (diagram_config.type() == diagram_t::kPackage) {
df.add_exclusive_filter(
std::make_unique<package_dependencies_filter_t>(
filter_t::kExclusive, diagram_config.exclude().dependencies,
relationship_t::kDependency, true));
df.add_exclusive_filter(
std::make_unique<package_dependants_filter_t>(
filter_t::kExclusive, diagram_config.exclude().dependants,
relationship_t::kDependency));
}
else if (diagram_config.type() == diagram_t::kInclude) {
std::vector<std::string> dependants;
std::vector<std::string> dependencies;
for (auto &&path : diagram_config.exclude().dependants) {
if (auto p = path.get<std::string>(); p.has_value()) {
std::filesystem::path dep_path{*p};
dependants.emplace_back(
dep_path.lexically_normal().string());
}
}
for (auto &&path : diagram_config.exclude().dependencies) {
if (auto p = path.get<std::string>(); p.has_value()) {
std::filesystem::path dep_path{*p};
dependencies.emplace_back(
dep_path.lexically_normal().string());
}
}
df.add_exclusive_filter(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kExclusive, dependants,
relationship_t::kAssociation, false));
df.add_exclusive_filter(
std::make_unique<source_file_dependency_filter_t>(
filter_t::kExclusive, dependencies,
relationship_t::kAssociation, true));
}
df.add_exclusive_filter(std::make_unique<context_filter>(
filter_t::kExclusive, diagram_config.exclude().context));
}
}
template <>
void advanced_diagram_filter_initializer::add_filter<
source_file_dependency_filter_t>(const filter_t &filter_type,
const std::vector<common::string_or_regex> &filter_config,
std::vector<std::unique_ptr<filter_visitor>> &result,
relationship_t &&rt, // NOLINT
bool &&direction // NOLINT
)
{
std::vector<std::string> deps;
for (auto &&path : filter_config) {
if (auto p = path.get<std::string>(); p.has_value()) {
const std::filesystem::path dep_path{*p};
deps.emplace_back(dep_path.lexically_normal().string());
}
}
result.emplace_back(std::make_unique<source_file_dependency_filter_t>(
filter_type, deps, rt, direction));
}
std::vector<std::unique_ptr<filter_visitor>>
advanced_diagram_filter_initializer::build(
filter_t filter_type, const config::filter &filter_config)
{
std::vector<std::unique_ptr<filter_visitor>> result;
// At any level, only allof, anyof, or a set of other non-operator
// filters can be present
if (filter_config.allof) {
std::vector<std::unique_ptr<filter_visitor>> allof_filters =
build(filter_type, *filter_config.allof);
auto allof_filter = std::make_unique<common::model::allof_filter>(
filter_type, std::move(allof_filters));
result.emplace_back(std::move(allof_filter));
}
if (filter_config.anyof) {
std::vector<std::unique_ptr<filter_visitor>> anyof_filters =
build(filter_type, *filter_config.anyof);
auto anyof_filter = std::make_unique<common::model::anyof_filter>(
filter_type, std::move(anyof_filters));
result.emplace_back(std::move(anyof_filter));
}
add_filter<namespace_filter>(filter_type, filter_config.namespaces, result);
add_filter<modules_filter>(filter_type, filter_config.modules, result);
add_filter<module_access_filter>(
filter_type, filter_config.module_access, result);
add_filter<relationship_filter>(
filter_type, filter_config.relationships, result);
add_filter<access_filter>(filter_type, filter_config.access, result);
add_filter<method_type_filter>(
filter_type, filter_config.method_types, result);
add_filter<paths_filter>(filter_type, filter_config.paths, result,
diagram_config.root_directory());
add_filter<element_filter>(filter_type, filter_config.elements, result);
add_filter<element_type_filter>(
filter_type, filter_config.element_types, result);
add_filter<subclass_filter>(filter_type, filter_config.subclasses, result);
add_filter<parents_filter>(filter_type, filter_config.parents, result);
add_filter<specializations_filter_t>(filter_type,
filter_config.specializations, result, relationship_t::kInstantiation,
false);
add_filter<class_dependants_filter_t>(filter_type, filter_config.dependants,
result, relationship_t::kDependency, false);
add_filter<class_dependencies_filter_t>(filter_type,
filter_config.dependencies, result, relationship_t::kDependency, true);
add_filter<callee_filter>(filter_type, filter_config.callee_types, result);
add_filter<package_dependants_filter_t>(filter_type,
filter_config.dependants, result, relationship_t::kDependency, false);
add_filter<package_dependencies_filter_t>(filter_type,
filter_config.dependencies, result, relationship_t::kDependency, true);
add_filter<context_filter>(filter_type, filter_config.context, result);
if (diagram_config.type() == diagram_t::kInclude) {
add_filter<source_file_dependency_filter_t>(filter_type,
filter_config.dependants, result, relationship_t::kAssociation,
false);
add_filter<source_file_dependency_filter_t>(filter_type,
filter_config.dependencies, result, relationship_t::kAssociation,
true);
}
return result;
}
void advanced_diagram_filter_initializer::initialize()
{
if (diagram_config.include) {
auto inclusive_filter =
build(filter_t::kInclusive, diagram_config.include());
for (auto &f : inclusive_filter)
df.add_filter(filter_t::kInclusive, std::move(f));
}
if (diagram_config.exclude) {
auto exclusive_filter =
build(filter_t::kExclusive, diagram_config.exclude());
for (auto &f : exclusive_filter)
df.add_filter(filter_t::kExclusive, std::move(f));
}
}
} // namespace clanguml::common::model

View File

@@ -0,0 +1,126 @@
/**
* @file src/common/model/filters/diagram_filter_factory.h
*
* Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "diagram_filter.h"
#include "package_diagram/model/diagram.h"
namespace clanguml::common::model {
using specializations_filter_t =
edge_traversal_filter<class_diagram::model::diagram,
class_diagram::model::class_, common::string_or_regex>;
using class_dependants_filter_t =
edge_traversal_filter<class_diagram::model::diagram,
class_diagram::model::class_, common::string_or_regex>;
using class_dependencies_filter_t =
edge_traversal_filter<class_diagram::model::diagram,
class_diagram::model::class_, common::string_or_regex>;
using package_dependants_filter_t =
edge_traversal_filter<package_diagram::model::diagram,
common::model::package, common::string_or_regex>;
using package_dependencies_filter_t =
edge_traversal_filter<package_diagram::model::diagram,
common::model::package, common::string_or_regex>;
using source_file_dependency_filter_t =
edge_traversal_filter<include_diagram::model::diagram,
common::model::source_file, std::string, common::model::source_file>;
class diagram_filter_initializer {
public:
diagram_filter_initializer(const config::diagram &c, diagram_filter &filter)
: diagram_config{c}
, df{filter}
{
}
virtual ~diagram_filter_initializer() = default;
virtual void initialize() = 0;
protected:
const config::diagram &diagram_config;
diagram_filter &df;
};
class basic_diagram_filter_initializer : public diagram_filter_initializer {
public:
using diagram_filter_initializer::diagram_filter_initializer;
~basic_diagram_filter_initializer() override = default;
void initialize() override;
};
class advanced_diagram_filter_initializer : public diagram_filter_initializer {
public:
using diagram_filter_initializer::diagram_filter_initializer;
~advanced_diagram_filter_initializer() override = default;
void initialize() override;
private:
std::vector<std::unique_ptr<filter_visitor>> build(
filter_t filter_type, const config::filter &filter_config);
template <typename FT, typename T, typename... Args>
void add_filter(const filter_t &filter_type,
const std::vector<T> &filter_config,
std::vector<std::unique_ptr<filter_visitor>> &result, Args &&...args)
{
if (!filter_config.empty()) {
auto filter = std::make_unique<FT>(
filter_type, filter_config, std::forward<Args>(args)...);
filter->set_mode(filter_mode_t::advanced);
result.emplace_back(std::move(filter));
}
}
};
template <>
void advanced_diagram_filter_initializer::add_filter<
source_file_dependency_filter_t>(const filter_t &filter_type,
const std::vector<common::string_or_regex> &filter_config,
std::vector<std::unique_ptr<filter_visitor>> &result, relationship_t &&rt,
bool &&direction);
class diagram_filter_factory {
public:
static std::unique_ptr<diagram_filter> create(
const common::model::diagram &d, const config::diagram &c)
{
auto filter = std::make_unique<diagram_filter>(
d, c, diagram_filter::private_constructor_tag_t{});
if (c.filter_mode() == config::filter_mode_t::basic) {
basic_diagram_filter_initializer init{c, *filter};
init.initialize();
}
else {
advanced_diagram_filter_initializer init{c, *filter};
init.initialize();
}
return filter;
}
};
} // namespace clanguml::common::model

View File

@@ -21,6 +21,7 @@
#include <iostream>
#include <optional>
#include <set>
#include <string>
#include <vector>
@@ -245,6 +246,23 @@ public:
}
}
void remove(const std::set<eid_t> &element_ids)
{
// First remove all matching elements on this level
elements_.erase(std::remove_if(elements_.begin(), elements_.end(),
[&element_ids](auto &&e) {
return element_ids.count(e->id()) > 0;
}),
elements_.end());
// Now recurse to any packages on this level
for (auto &p : elements_) {
if (dynamic_cast<nested_trait<T, Path> *>(p.get()))
dynamic_cast<nested_trait<T, Path> *>(p.get())->remove(
element_ids);
}
}
private:
std::vector<std::unique_ptr<T>> elements_;
};

View File

@@ -253,8 +253,7 @@ public:
comment_visitor_->visit(decl, e);
const auto *comment =
decl.getASTContext().getRawCommentForDeclNoCache(&decl);
auto *comment = decl.getASTContext().getRawCommentForDeclNoCache(&decl);
process_comment(comment, decl.getASTContext().getDiagnostics(), e);
}
@@ -290,21 +289,30 @@ public:
return stripped_comment;
}
bool skip_system_header_decl(const clang::NamedDecl *decl) const
{
return !config().include_system_headers() &&
source_manager().isInSystemHeader(
decl->getSourceRange().getBegin());
}
/**
* @brief Check if the diagram should include a declaration.
*
* @param decl Clang declaration.
* @return True, if the entity should be included in the diagram.
*/
bool should_include(const clang::NamedDecl *decl)
bool should_include(const clang::NamedDecl *decl) const
{
if (decl == nullptr)
return false;
if (source_manager().isInSystemHeader(
decl->getSourceRange().getBegin()))
if (skip_system_header_decl(decl))
return false;
if (config().filter_mode() == config::filter_mode_t::advanced)
return true;
auto should_include_namespace = diagram().should_include(
common::model::namespace_{decl->getQualifiedNameAsString()});

View File

@@ -186,6 +186,19 @@ std::string to_string(context_direction_t cd)
}
}
std::string to_string(filter_mode_t fm)
{
switch (fm) {
case filter_mode_t::basic:
return "basic";
case filter_mode_t::advanced:
return "advanced";
default:
assert(false);
return "";
}
}
std::optional<std::string> plantuml::get_style(
const common::model::relationship_t relationship_type) const
{
@@ -228,6 +241,8 @@ void inheritable_diagram_options::inherit(
using_module.override(parent.using_module);
include_relations_also_as_members.override(
parent.include_relations_also_as_members);
filter_mode.override(parent.filter_mode);
include_system_headers.override(parent.include_system_headers);
include.override(parent.include);
exclude.override(parent.exclude);
puml.override(parent.puml);

View File

@@ -116,6 +116,13 @@ struct plantuml_keyword_mapping_t {
relationships;
};
enum class filter_mode_t {
basic, /*!< Default filter structure without logical operators */
advanced /*!< Advanced filter config with logical operators */
};
std::string to_string(filter_mode_t cp);
/**
* @brief PlantUML diagram config section
*
@@ -187,6 +194,9 @@ struct diagram_template {
};
struct filter {
std::shared_ptr<filter> anyof;
std::shared_ptr<filter> allof;
/*! @brief Namespaces filter
*
* Example:
@@ -445,8 +455,20 @@ struct layout_hint {
using layout_hints = std::map<std::string, std::vector<layout_hint>>;
struct generate_links_config {
std::string link;
std::string tooltip;
std::map</* path */ std::string, /* pattern */ std::string> link;
std::map</* path */ std::string, /* pattern */ std::string> tooltip;
std::optional<std::pair<std::string, std::string>> get_link_pattern(
const std::string &path) const
{
return util::find_entry_by_path_prefix(link, path);
}
std::optional<std::pair<std::string, std::string>> get_tooltip_pattern(
const std::string &path) const
{
return util::find_entry_by_path_prefix(tooltip, path);
}
};
struct git_config {
@@ -544,6 +566,8 @@ struct inheritable_diagram_options {
option<std::string> using_module{"using_module"};
option<bool> include_relations_also_as_members{
"include_relations_also_as_members", true};
option<filter_mode_t> filter_mode{"filter_mode", filter_mode_t::basic};
option<bool> include_system_headers{"include_system_headers", false};
option<filter> include{"include"};
option<filter> exclude{"exclude"};
option<plantuml> puml{"plantuml", option_inherit_mode::kAppend};

View File

@@ -37,8 +37,8 @@ types:
- abbreviated
- none
generate_links_t:
link: string
tooltip: string
link: !optional [string, map_t<string;string>]
tooltip: !optional [string, map_t<string;string>]
git_t:
branch: string
revision: [string, int]
@@ -156,6 +156,8 @@ types:
paths: !optional [string]
method_types: !optional [method_type_filter_t]
callee_types: !optional [callee_type_filter_t]
anyof: !optional filter_t
allof: !optional filter_t
function_location_t:
function: string
marker_location_t:
@@ -163,6 +165,9 @@ types:
source_location_t:
- function_location_t
- marker_location_t
filter_mode_t: !variant
- basic
- advanced
class_diagram_t:
type: !variant [class]
#
@@ -171,6 +176,8 @@ types:
__parent_path: !optional string
comment_parser: !optional comment_parser_t
debug_mode: !optional bool
filter_mode: !optional filter_mode_t
include_system_headers: !optional bool
exclude: !optional filter_t
generate_links: !optional generate_links_t
git: !optional git_t
@@ -215,6 +222,8 @@ types:
debug_mode: !optional bool
exclude: !optional filter_t
generate_links: !optional generate_links_t
filter_mode: !optional filter_mode_t
include_system_headers: !optional bool
git: !optional git_t
glob: !optional [string]
include: !optional filter_t
@@ -258,6 +267,8 @@ types:
generate_links: !optional generate_links_t
git: !optional git_t
glob: !optional [string]
filter_mode: !optional filter_mode_t
include_system_headers: !optional bool
include: !optional filter_t
plantuml: !optional
before: !optional [string]
@@ -286,6 +297,8 @@ types:
__parent_path: !optional string
comment_parser: !optional comment_parser_t
debug_mode: !optional bool
filter_mode: !optional filter_mode_t
include_system_headers: !optional bool
exclude: !optional filter_t
generate_links: !optional generate_links_t
git: !optional git_t
@@ -371,6 +384,8 @@ root:
generate_template_argument_dependencies: !optional bool
skip_redundant_dependencies: !optional bool
type_aliases: !optional map_t<string;string>
filter_mode: !optional filter_mode_t
include_system_headers: !optional bool
)";
} // namespace clanguml::config

View File

@@ -158,6 +158,21 @@ void get_option<clanguml::config::comment_parser_t>(const Node &node,
}
}
template <>
void get_option<clanguml::config::filter_mode_t>(const Node &node,
clanguml::config::option<clanguml::config::filter_mode_t> &option)
{
if (node[option.name]) {
const auto &val = node[option.name].as<std::string>();
if (val == "basic")
option.set(clanguml::config::filter_mode_t::basic);
else if (val == "advanced")
option.set(clanguml::config::filter_mode_t::advanced);
else
throw std::runtime_error("Invalid comment_parser value: " + val);
}
}
template <>
void get_option<std::map<std::string, clanguml::config::diagram_template>>(
const Node &node,
@@ -521,6 +536,14 @@ template <> struct convert<namespace_or_regex> {
template <> struct convert<filter> {
static bool decode(const Node &node, filter &rhs)
{
if (node["anyof"]) {
rhs.anyof = std::make_unique<filter>(node["anyof"].as<filter>());
}
if (node["allof"]) {
rhs.allof = std::make_unique<filter>(node["allof"].as<filter>());
}
if (node["namespaces"]) {
auto namespace_list =
node["namespaces"].as<decltype(rhs.namespaces)>();
@@ -593,11 +616,19 @@ template <> struct convert<filter> {
template <> struct convert<generate_links_config> {
static bool decode(const Node &node, generate_links_config &rhs)
{
if (node["link"])
rhs.link = node["link"].as<decltype(rhs.link)>();
if (node["link"]) {
if (node["link"].IsMap())
rhs.link = node["link"].as<decltype(rhs.link)>();
else
rhs.link.emplace(".", node["link"].as<std::string>());
}
if (node["tooltip"])
rhs.tooltip = node["tooltip"].as<decltype(rhs.tooltip)>();
if (node["tooltip"]) {
if (node["tooltip"].IsMap())
rhs.tooltip = node["tooltip"].as<decltype(rhs.tooltip)>();
else
rhs.tooltip.emplace(".", node["tooltip"].as<std::string>());
}
return true;
}
@@ -631,6 +662,8 @@ template <typename T> bool decode_diagram(const Node &node, T &rhs)
get_option(node, rhs.glob);
get_option(node, rhs.using_namespace);
get_option(node, rhs.using_module);
get_option(node, rhs.filter_mode);
get_option(node, rhs.include_system_headers);
get_option(node, rhs.include);
get_option(node, rhs.exclude);
get_option(node, rhs.puml);
@@ -849,6 +882,7 @@ template <> struct convert<config> {
get_option(node, rhs.glob);
get_option(node, rhs.using_namespace);
get_option(node, rhs.using_module);
get_option(node, rhs.filter_mode);
get_option(node, rhs.output_directory);
get_option(node, rhs.query_driver);
get_option(node, rhs.allow_empty_diagrams);

View File

@@ -39,14 +39,11 @@ void generator::generate_relationships(
});
}
else {
util::for_each_if(
f.relationships(),
[this](const auto &r) { return model().should_include(r.type()); },
[&f, &parent](const auto &r) {
nlohmann::json rel = r;
rel["source"] = std::to_string(f.id().value());
parent["relationships"].push_back(std::move(rel));
});
for (const auto &r : f.relationships()) {
nlohmann::json rel = r;
rel["source"] = std::to_string(f.id().value());
parent["relationships"].push_back(std::move(rel));
}
}
}
@@ -73,17 +70,15 @@ void generator::generate(const source_file &f, nlohmann::json &parent) const
parent["elements"].push_back(std::move(j));
}
else {
if (model().should_include(f)) {
LOG_DBG("Generating file {}", f.name());
LOG_DBG("Generating file {}", f.name());
j["type"] = "file";
j["file_kind"] = to_string(f.type());
if (f.type() == common::model::source_file_t::kHeader) {
j["is_system"] = f.is_system_header();
}
parent["elements"].push_back(std::move(j));
j["type"] = "file";
j["file_kind"] = to_string(f.type());
if (f.type() == common::model::source_file_t::kHeader) {
j["is_system"] = f.is_system_header();
}
parent["elements"].push_back(std::move(j));
}
}

View File

@@ -49,21 +49,13 @@ void generator::generate_relationships(
});
}
else {
util::for_each_if(
f.relationships(),
[this](const auto &r) {
return model().should_include(r.type()) &&
util::contains(m_generated_aliases,
model().get(r.destination()).value().alias());
},
[&f, &ostr, this](const auto &r) {
ostr << indent(1) << f.alias() << " "
<< (r.type() == common::model::relationship_t::kDependency
? "-.->"
: "-->")
<< " " << model().get(r.destination()).value().alias()
<< '\n';
});
for (const auto &r : f.relationships()) {
ostr << indent(1) << f.alias() << " "
<< (r.type() == common::model::relationship_t::kDependency
? "-.->"
: "-->")
<< " " << model().get(r.destination()).value().alias() << '\n';
}
}
}
@@ -86,11 +78,9 @@ void generator::generate(const source_file &f, std::ostream &ostr) const
else {
LOG_DBG("Generating file {}", f.name());
if (model().should_include(f)) {
ostr << indent(1) << f.alias() << "[" << f.name() << "]\n";
ostr << indent(1) << f.alias() << "[" << f.name() << "]\n";
m_generated_aliases.emplace(f.alias());
}
m_generated_aliases.emplace(f.alias());
}
if (config().generate_links) {

View File

@@ -44,18 +44,11 @@ void generator::generate_relationships(
});
}
else {
util::for_each_if(
f.relationships(),
[this](const auto &r) {
return model().should_include(r.type()) &&
util::contains(m_generated_aliases,
model().get(r.destination()).value().alias());
},
[&f, &ostr, this](const auto &r) {
ostr << f.alias() << " "
<< plantuml_common::to_plantuml(r, config()) << " "
<< model().get(r.destination()).value().alias() << '\n';
});
for (const auto &r : f.relationships()) {
ostr << f.alias() << " "
<< plantuml_common::to_plantuml(r, config()) << " "
<< model().get(r.destination()).value().alias() << '\n';
}
}
}
@@ -79,17 +72,15 @@ void generator::generate(const source_file &f, std::ostream &ostr) const
else {
LOG_DBG("Generating file {}", f.name());
if (model().should_include(f)) {
ostr << "file \"" << f.name() << "\" as " << f.alias();
ostr << "file \"" << f.name() << "\" as " << f.alias();
if (config().generate_links) {
generate_link(ostr, f);
}
ostr << '\n';
m_generated_aliases.emplace(f.alias());
if (config().generate_links) {
generate_link(ostr, f);
}
ostr << '\n';
m_generated_aliases.emplace(f.alias());
}
}

View File

@@ -18,6 +18,7 @@
#include "diagram.h"
#include "common/model/filters/diagram_filter.h"
#include "util/error.h"
#include "util/util.h"
@@ -53,8 +54,6 @@ void diagram::add_file(std::unique_ptr<common::model::source_file> &&f)
assert(!ff.name().empty());
assert(ff.id().value() != 0);
element_view<source_file>::add(ff);
auto p = ff.path();
if (!f->path().is_empty()) {
@@ -86,7 +85,8 @@ void diagram::add_file(std::unique_ptr<common::model::source_file> &&f)
assert(p.type() == common::model::path_type::kFilesystem);
add_element(p, std::move(f));
if (add_element(p, std::move(f)))
element_view<source_file>::add(ff);
}
const common::reference_vector<common::model::source_file> &
@@ -133,6 +133,25 @@ inja::json diagram::context() const
return ctx;
}
void diagram::apply_filter()
{
// First find all element ids which should be removed
std::set<eid_t> to_remove;
for (auto &f : element_view<source_file>::view())
if (f.get().type() != common::model::source_file_t::kDirectory &&
!filter().should_include(f.get())) {
to_remove.emplace(f.get().id());
}
for (auto &sf : element_view<source_file>::view())
sf.get().apply_filter(filter(), to_remove);
element_view<source_file>::remove(to_remove);
nested_trait_fspath::remove(to_remove);
}
bool diagram::is_empty() const { return element_view<source_file>::is_empty(); }
} // namespace clanguml::include_diagram::model

View File

@@ -33,13 +33,15 @@ using clanguml::common::opt_ref;
using clanguml::common::model::diagram_element;
using clanguml::common::model::source_file;
using nested_trait_fspath = clanguml::common::model::nested_trait<source_file,
clanguml::common::model::filesystem_path>;
/**
* @brief Class representing an include diagram model.
*/
class diagram : public clanguml::common::model::diagram,
public clanguml::common::model::element_view<source_file>,
public clanguml::common::model::nested_trait<source_file,
clanguml::common::model::filesystem_path> {
public nested_trait_fspath {
public:
diagram() = default;
@@ -128,6 +130,8 @@ public:
* @return True, if diagram is empty
*/
bool is_empty() const override;
void apply_filter() override;
};
template <typename ElementT>

View File

@@ -39,9 +39,7 @@ void generator::generate_relationships(
auto destination_package = model().get(r.destination());
if (!destination_package ||
!model().should_include(
dynamic_cast<const package &>(*destination_package)))
if (!destination_package)
continue;
rel["source"] = std::to_string(p.id().value());
@@ -88,9 +86,7 @@ void generator::generate(const package &p, nlohmann::json &parent) const
for (const auto &subpackage : p) {
auto &pkg = dynamic_cast<package &>(*subpackage);
if (model().should_include(pkg)) {
generate(pkg, j);
}
generate(pkg, j);
}
parent["elements"].push_back(std::move(j));
@@ -98,9 +94,7 @@ void generator::generate(const package &p, nlohmann::json &parent) const
else {
for (const auto &subpackage : p) {
auto &pkg = dynamic_cast<package &>(*subpackage);
if (model().should_include(pkg)) {
generate(pkg, parent);
}
generate(pkg, parent);
}
}
}
@@ -120,15 +114,12 @@ void generator::generate_diagram(nlohmann::json &parent) const
for (const auto &p : model()) {
auto &pkg = dynamic_cast<package &>(*p);
if (model().should_include(pkg)) {
generate(pkg, parent);
}
generate(pkg, parent);
}
// Process package relationships
for (const auto &p : model()) {
if (model().should_include(dynamic_cast<package &>(*p)))
generate_relationships(dynamic_cast<package &>(*p), parent);
generate_relationships(dynamic_cast<package &>(*p), parent);
}
}

View File

@@ -47,9 +47,7 @@ void generator::generate_relationships(
try {
auto destination_package = model().get(r.destination());
if (!destination_package ||
!model().should_include(
dynamic_cast<const package &>(*destination_package)))
if (!destination_package)
continue;
auto destination_alias = model().to_alias(r.destination());
@@ -94,16 +92,12 @@ void generator::generate(const package &p, std::ostream &ostr) const
for (const auto &subpackage : p) {
auto &pkg = dynamic_cast<package &>(*subpackage);
if (model().should_include(pkg)) {
auto together_group =
config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
auto together_group = config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
}
@@ -159,16 +153,12 @@ void generator::generate_diagram(std::ostream &ostr) const
{
for (const auto &p : model()) {
auto &pkg = dynamic_cast<package &>(*p);
if (model().should_include(pkg)) {
auto together_group =
config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
auto together_group = config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
}
@@ -176,8 +166,7 @@ void generator::generate_diagram(std::ostream &ostr) const
// Process package relationships
for (const auto &p : model()) {
if (model().should_include(dynamic_cast<package &>(*p)))
generate_relationships(dynamic_cast<package &>(*p), ostr);
generate_relationships(dynamic_cast<package &>(*p), ostr);
}
}

View File

@@ -39,10 +39,7 @@ void generator::generate_relationships(
std::stringstream relstr;
try {
auto destination_package = model().get(r.destination());
if (!destination_package ||
!model().should_include(
dynamic_cast<const package &>(*destination_package)))
if (!destination_package)
continue;
auto destination_alias = model().to_alias(r.destination());
@@ -62,9 +59,8 @@ void generator::generate_relationships(
// Process it's subpackages relationships
for (const auto &subpackage : p) {
if (model().should_include(dynamic_cast<const package &>(*subpackage)))
generate_relationships(
dynamic_cast<const package &>(*subpackage), ostr);
generate_relationships(
dynamic_cast<const package &>(*subpackage), ostr);
}
}
@@ -96,16 +92,12 @@ void generator::generate(const package &p, std::ostream &ostr) const
for (const auto &subpackage : p) {
auto &pkg = dynamic_cast<package &>(*subpackage);
if (model().should_include(pkg)) {
auto together_group =
config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
auto together_group = config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
}
@@ -138,16 +130,12 @@ void generator::generate_diagram(std::ostream &ostr) const
{
for (const auto &p : model()) {
auto &pkg = dynamic_cast<package &>(*p);
if (model().should_include(pkg)) {
auto together_group =
config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(
together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
auto together_group = config().get_together_group(pkg.full_name(false));
if (together_group) {
together_group_stack_.group_together(together_group.value(), &pkg);
}
else {
generate(pkg, ostr);
}
}
@@ -155,8 +143,7 @@ void generator::generate_diagram(std::ostream &ostr) const
// Process package relationships
for (const auto &p : model()) {
if (model().should_include(dynamic_cast<package &>(*p)))
generate_relationships(dynamic_cast<package &>(*p), ostr);
generate_relationships(dynamic_cast<package &>(*p), ostr);
}
generate_config_layout_hints(ostr);

View File

@@ -18,6 +18,7 @@
#include "diagram.h"
#include "common/model/filters/diagram_filter.h"
#include "util/error.h"
#include "util/util.h"
@@ -74,6 +75,23 @@ inja::json diagram::context() const
return ctx;
}
void diagram::apply_filter()
{
// First find all element ids which should be removed
std::set<eid_t> to_remove;
for (const auto &c : packages())
if (!filter().should_include(c.get()))
to_remove.emplace(c.get().id());
element_view<package>::remove(to_remove);
nested_trait_ns::remove(to_remove);
for (auto &c : element_view<package>::view())
c.get().apply_filter(filter(), to_remove);
}
bool diagram::is_empty() const { return element_view<package>::is_empty(); }
} // namespace clanguml::package_diagram::model

View File

@@ -32,14 +32,16 @@ using clanguml::common::model::diagram_element;
using clanguml::common::model::package;
using clanguml::common::model::path;
using nested_trait_ns =
clanguml::common::model::nested_trait<clanguml::common::model::element,
clanguml::common::model::namespace_>;
/**
* @brief Package diagram model.
*/
class diagram : public clanguml::common::model::diagram,
public clanguml::common::model::element_view<package>,
public clanguml::common::model::nested_trait<
clanguml::common::model::element,
clanguml::common::model::namespace_> {
public nested_trait_ns {
public:
diagram() = default;
@@ -165,6 +167,8 @@ public:
*/
bool is_empty() const override;
void apply_filter() override;
private:
/**
* @brief Add element using module as diagram path

View File

@@ -18,7 +18,7 @@
#include "diagram.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter.h"
#include <functional>
#include <memory>

View File

@@ -380,7 +380,7 @@ private:
struct method : public function {
method(const common::model::namespace_ &using_namespace);
method(const function &) = delete;
method(const method &) = delete;
method(method &&) noexcept = delete;
method &operator=(const method &) = delete;
method &operator=(method &&) = delete;

View File

@@ -518,7 +518,8 @@ bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr)
bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
{
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
if (!config().include_system_headers() &&
source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
return true;
LOG_TRACE("Entering call expression at {}",
@@ -541,7 +542,8 @@ bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
bool translation_unit_visitor::TraverseCUDAKernelCallExpr(
clang::CUDAKernelCallExpr *expr)
{
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
if (!config().include_system_headers() &&
source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
return true;
LOG_TRACE("Entering CUDA kernel call expression at {}",
@@ -564,7 +566,8 @@ bool translation_unit_visitor::TraverseCUDAKernelCallExpr(
bool translation_unit_visitor::TraverseCXXMemberCallExpr(
clang::CXXMemberCallExpr *expr)
{
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
if (!config().include_system_headers() &&
source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
return true;
LOG_TRACE("Entering member call expression at {}",
@@ -1085,6 +1088,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
auto generated_message_from_comment = generate_message_from_comment(m);
if (!generated_message_from_comment && !should_include(expr)) {
LOG_DBG("Skipping call expression due to filter at: {}",
expr->getBeginLoc().printToString(source_manager()));
processed_comments().erase(raw_expr_comment);
return true;
}
@@ -1178,7 +1184,7 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
auto success = process_function_call_expression(m, expr);
if (!success) {
LOG_DBG("Skipping call to call expression at: {}",
LOG_DBG("Skipping call expression at: {}",
expr->getBeginLoc().printToString(source_manager()));
return true;
@@ -1708,7 +1714,9 @@ translation_unit_visitor::create_class_model(clang::CXXRecordDecl *cls)
.get_participant<clanguml::sequence_diagram::model::class_>(
*id_opt);
assert(parent_class);
if (!parent_class) {
return {};
}
c.set_namespace(ns);
if (cls->getNameAsString().empty()) {
@@ -2096,6 +2104,7 @@ translation_unit_visitor::create_lambda_method_model(
ns.pop_back();
method_model_ptr->set_name(ns.name());
ns.pop_back();
method_model_ptr->set_namespace(ns);
method_model_ptr->is_defaulted(declaration->isDefaulted());
method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
@@ -2135,6 +2144,7 @@ translation_unit_visitor::create_method_model(clang::CXXMethodDecl *declaration)
ns.pop_back();
method_model_ptr->set_name(ns.name());
ns.pop_back();
method_model_ptr->set_namespace(ns);
method_model_ptr->is_defaulted(declaration->isDefaulted());
method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
@@ -2189,14 +2199,8 @@ translation_unit_visitor::create_method_model(clang::CXXMethodDecl *declaration)
bool translation_unit_visitor::should_include(const clang::TagDecl *decl) const
{
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
return false;
const auto decl_file = decl->getLocation().printToString(source_manager());
return diagram().should_include(
namespace_{decl->getQualifiedNameAsString()}) &&
diagram().should_include(common::model::source_file{decl_file});
return visitor_specialization_t::should_include(
dynamic_cast<const clang::NamedDecl *>(decl));
}
bool translation_unit_visitor::should_include(
@@ -2234,8 +2238,11 @@ bool translation_unit_visitor::should_include(const clang::CallExpr *expr) const
if (callee_decl != nullptr) {
const auto *callee_function = callee_decl->getAsFunction();
if ((callee_function == nullptr) || !should_include(callee_function))
if ((callee_function == nullptr) || !should_include(callee_function)) {
LOG_DBG("Skipping call expression at {}",
expr->getBeginLoc().printToString(source_manager()));
return false;
}
return should_include(callee_function);
}
@@ -2261,30 +2268,19 @@ bool translation_unit_visitor::should_include(
bool translation_unit_visitor::should_include(
const clang::FunctionDecl *decl) const
{
const auto decl_file = decl->getLocation().printToString(source_manager());
return diagram().should_include(
namespace_{decl->getQualifiedNameAsString()}) &&
diagram().should_include(common::model::source_file{decl_file});
return visitor_specialization_t::should_include(decl);
}
bool translation_unit_visitor::should_include(
const clang::FunctionTemplateDecl *decl) const
{
return should_include(decl->getAsFunction());
return visitor_specialization_t::should_include(decl->getAsFunction());
}
bool translation_unit_visitor::should_include(
const clang::ClassTemplateDecl *decl) const
{
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
return false;
const auto decl_file = decl->getLocation().printToString(source_manager());
return diagram().should_include(
namespace_{decl->getQualifiedNameAsString()}) &&
diagram().should_include(common::model::source_file{decl_file});
return visitor_specialization_t::should_include(decl);
}
std::optional<std::string> translation_unit_visitor::get_expression_comment(

View File

@@ -496,4 +496,75 @@ std::string format_message_comment(const std::string &comment, unsigned width)
return result;
}
std::filesystem::path normalize_relative_path(const std::filesystem::path &path)
{
if (path.is_absolute())
return path;
std::filesystem::path result;
for (const auto &part : path) {
if (part == ".") {
continue;
}
result /= part;
}
return result;
}
bool is_subpath(
const std::filesystem::path &path, const std::filesystem::path &base)
{
if (path.empty())
return false;
auto normalized_path = normalize_relative_path(path);
auto normalized_base = normalize_relative_path(base);
if (normalized_path == normalized_base)
return true;
auto rel = std::filesystem::relative(normalized_path, normalized_base);
std::string rel_str = rel.string();
return !rel_str.empty() && rel.native()[0] != '.';
}
std::optional<std::pair<std::string, std::string>> find_entry_by_path_prefix(
const std::map<std::string, std::string> &m, const std::string &path)
{
if (m.empty())
return {};
// Extract keys and sort them by length in descending order
std::vector<std::string> keys;
keys.reserve(m.size());
for (const auto &[key, pattern] : m) {
keys.push_back(key);
}
std::sort(keys.begin(), keys.end(),
[](const std::string &a, const std::string &b) {
return a.size() > b.size();
});
std::filesystem::path file_path{path};
for (const auto &key : keys) {
const auto &pattern = m.at(key);
std::filesystem::path key_path{key};
if (is_subpath(file_path, key_path)) {
return {{key, pattern}};
}
}
if ((path.empty() || file_path.is_relative() || path == ".") &&
m.count(".") > 0) {
return {{".", m.at(".")}};
}
return {};
}
} // namespace clanguml::util

View File

@@ -23,6 +23,8 @@
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <map>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>
@@ -460,4 +462,9 @@ bool is_relative_to(
std::string format_message_comment(
const std::string &c, unsigned width = kDefaultMessageCommentWidth);
bool is_subpath(
const std::filesystem::path &path, const std::filesystem::path &prefix);
std::optional<std::pair<std::string, std::string>> find_entry_by_path_prefix(
const std::map<std::string, std::string> &m, const std::string &prefix);
} // namespace clanguml::util

View File

@@ -93,6 +93,7 @@ set(TEST_NAMES
test_config
test_cli_handler
test_filters
test_filters_advanced
test_thread_pool_executor
test_query_driver_output_extractor
test_progress_indicator)

15
tests/t00080/.clang-uml Normal file
View File

@@ -0,0 +1,15 @@
diagrams:
t00080_class:
type: class
filter_mode: advanced
comment_parser: clang
include_system_headers: true
glob:
- t00080.cc
include:
anyof:
namespaces:
- clanguml::t00080
elements:
- std::thread
using_namespace: clanguml::t00080

24
tests/t00080/t00080.cc Normal file
View File

@@ -0,0 +1,24 @@
#include <thread>
namespace clanguml {
namespace t00080 {
class Worker : public std::thread {
public:
using std::thread::thread;
~Worker()
{
if (this->joinable()) {
this->join();
}
}
void start(int delay) { }
};
struct R {
Worker *w;
};
}
}

35
tests/t00080/test_case.h Normal file
View File

@@ -0,0 +1,35 @@
/**
* tests/t00080/test_case.h
*
* Copyright (c) 2021-2024 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("t00080")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00080", "t00080_class");
CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(IsClass(src, "Worker"));
REQUIRE(IsClass(src, "R"));
REQUIRE(IsClass(src, "std::thread"));
REQUIRE(!IsClass(src, "std::jthread"));
REQUIRE(IsAssociation<Public>(src, "R", "Worker", "w"));
});
}

25
tests/t00081/.clang-uml Normal file
View File

@@ -0,0 +1,25 @@
diagrams:
t00081_class:
type: class
glob:
- t00081.cc
filter_mode: advanced
include_system_headers: true
include:
allof:
namespaces:
- clanguml::t00081
- std
context:
- match:
radius: 2
pattern: clanguml::t00081::A
exclude:
anyof:
access:
- private
- public
- protected
relationships:
- dependency
using_namespace: clanguml::t00081

18
tests/t00081/t00081.cc Normal file
View File

@@ -0,0 +1,18 @@
#include <map>
#include <string>
#include <vector>
namespace clanguml {
namespace t00081_detail {
struct C { };
} // namespace t00081_detail
namespace t00081 {
struct A {
std::vector<std::string> as;
std::string s;
std::map<std::string, std::string> ms;
t00081_detail::C *c;
};
} // namespace t00081
} // namespace clanguml

35
tests/t00081/test_case.h Normal file
View File

@@ -0,0 +1,35 @@
/**
* tests/t00081/test_case.h
*
* Copyright (c) 2021-2024 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("t00081")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00081", "t00081_class");
CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(IsClass(src, "A"));
REQUIRE(!IsClass(src, "C"));
REQUIRE(IsClass(src, "std::string"));
REQUIRE(IsClass(src, "std::vector<std::string>"));
REQUIRE(IsClass(src, "std::map<std::string,std::string>"));
});
}

20
tests/t00082/.clang-uml Normal file
View File

@@ -0,0 +1,20 @@
diagrams:
t00082_class:
type: class
glob:
- t00082.cc
generate_packages: true
filter_mode: advanced
include:
anyof:
subclasses:
- clanguml::t00082::ns1::nsA::A1
namespaces:
- clanguml::t00082::ns2::nsB
context:
- clanguml::t00082::ns3::nsC::B3
exclude:
allof:
elements:
- clanguml::t00082::ns1::nsA::A1
using_namespace: clanguml::t00082

30
tests/t00082/t00082.cc Normal file
View File

@@ -0,0 +1,30 @@
namespace clanguml::t00082 {
namespace ns1 {
namespace nsA {
struct A1 { };
struct B1 : public A1 { };
struct C1 : public B1 { };
struct D1 { };
}
}
namespace ns2 {
namespace nsB {
struct A2 { };
struct B2 : public A2 { };
struct C2 : public B2 { };
}
}
namespace ns3 {
namespace nsC {
struct A3 { };
struct B3 : public A3 { };
struct C3 : public B3 { };
struct D3 { };
}
}
namespace ns4 {
namespace nsD {
struct A4 { };
}
}
}

46
tests/t00082/test_case.h Normal file
View File

@@ -0,0 +1,46 @@
/**
* tests/t00082/test_case.h
*
* Copyright (c) 2021-2024 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("t00082")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00082", "t00082_class");
CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(!IsClass(src, {"ns1::nsA", "A1"}));
REQUIRE(IsClass(src, {"ns1::nsA", "B1"}));
REQUIRE(IsClass(src, {"ns1::nsA", "C1"}));
REQUIRE(!IsClass(src, {"ns1::nsA", "D1"}));
REQUIRE(IsClass(src, {"ns2::nsB", "A2"}));
REQUIRE(IsClass(src, {"ns2::nsB", "B2"}));
REQUIRE(IsClass(src, {"ns2::nsB", "C2"}));
REQUIRE(IsClass(src, {"ns3::nsC", "A3"}));
REQUIRE(IsClass(src, {"ns3::nsC", "B3"}));
REQUIRE(IsClass(src, {"ns3::nsC", "C3"}));
REQUIRE(!IsClass(src, {"ns3::nsC", "D3"}));
REQUIRE(!IsNamespacePackage(src, "ns4"s));
REQUIRE(!IsClass(src, {"ns4::nsD", "A4"}));
});
}

16
tests/t00083/.clang-uml Normal file
View File

@@ -0,0 +1,16 @@
diagrams:
t00083_class:
type: class
glob:
- t00083.cc
generate_packages: true
filter_mode: advanced
exclude:
anyof:
subclasses:
- clanguml::t00083::ns1::nsA::A1
namespaces:
- clanguml::t00083::ns2::nsB
context:
- clanguml::t00083::ns3::nsC::B3
using_namespace: clanguml::t00083

30
tests/t00083/t00083.cc Normal file
View File

@@ -0,0 +1,30 @@
namespace clanguml::t00083 {
namespace ns1 {
namespace nsA {
struct A1 { };
struct B1 : public A1 { };
struct C1 : public B1 { };
struct D1 { };
}
}
namespace ns2 {
namespace nsB {
struct A2 { };
struct B2 : public A2 { };
struct C2 : public B2 { };
}
}
namespace ns3 {
namespace nsC {
struct A3 { };
struct B3 : public A3 { };
struct C3 : public B3 { };
struct D3 { };
}
}
namespace ns4 {
namespace nsD {
struct A4 { };
}
}
}

46
tests/t00083/test_case.h Normal file
View File

@@ -0,0 +1,46 @@
/**
* tests/t00083/test_case.h
*
* Copyright (c) 2021-2024 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("t00083")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00083", "t00083_class");
CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(!IsClass(src, {"ns1::nsA", "A1"}));
REQUIRE(!IsClass(src, {"ns1::nsA", "B1"}));
REQUIRE(!IsClass(src, {"ns1::nsA", "C1"}));
REQUIRE(IsClass(src, {"ns1::nsA", "D1"}));
REQUIRE(!IsClass(src, {"ns2::nsB", "A2"}));
REQUIRE(!IsClass(src, {"ns2::nsB", "B2"}));
REQUIRE(!IsClass(src, {"ns2::nsB", "C2"}));
REQUIRE(!IsClass(src, {"ns3::nsC", "A3"}));
REQUIRE(!IsClass(src, {"ns3::nsC", "B3"}));
REQUIRE(!IsClass(src, {"ns3::nsC", "C3"}));
REQUIRE(IsClass(src, {"ns3::nsC", "D3"}));
REQUIRE(IsNamespacePackage(src, "ns4"s));
REQUIRE(IsClass(src, {"ns4::nsD", "A4"}));
});
}

View File

@@ -1,7 +1,3 @@
#include <algorithm>
#include <numeric>
#include <vector>
namespace clanguml {
namespace t20002 {

16
tests/t20055/.clang-uml Normal file
View File

@@ -0,0 +1,16 @@
diagrams:
t20055_sequence:
type: sequence
filter_mode: advanced
glob:
- t20055.cc
include:
anyof:
namespaces:
- clanguml::t20055::ns2
elements:
- clanguml::t20055::ns1::B
- clanguml::t20055::ns1::d()
using_namespace: clanguml::t20055
from:
- function: "clanguml::t20055::ns2::tmain()"

40
tests/t20055/t20055.cc Normal file
View File

@@ -0,0 +1,40 @@
namespace clanguml {
namespace t20055 {
namespace ns1 {
void d() { }
struct A {
void a() { }
};
struct B {
A a;
void b()
{
a.a();
d();
}
};
} // namespace ns1
namespace ns2 {
void f() { }
struct C {
ns1::B b;
void c()
{
b.b();
f();
}
};
void tmain()
{
C c;
c.c();
}
} // namespace ns2
}
}

40
tests/t20055/test_case.h Normal file
View File

@@ -0,0 +1,40 @@
/**
* tests/t20055/test_case.h
*
* Copyright (c) 2021-2024 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("t20055")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_SEQUENCE_MODEL("t20055", "t20055_sequence");
CHECK_SEQUENCE_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(MessageOrder(src,
{
//
{{"ns2", "tmain()"}, {"ns2", "C"}, "c()"},
{{"ns2", "C"}, {"ns1", "B"}, "b()"},
{{"ns1", "B"}, {"ns1", "d()"}, ""},
{{"ns2", "C"}, {"ns2", "f()"}, ""}
//
}));
REQUIRE(!HasMessage(src, {{"ns1", "B"}, {"ns1", "A"}, "a()"}));
});
}

View File

@@ -9,25 +9,6 @@ module;
export module t30015.app;
import t30015.lib1;
// import t30015.app;
// import t30015.mod2;
// import t30015.mod3;
// import t30015.mod4;
// import t30015.mod5;
// import t30015.mod6;
// import t30015.mod7;
// import t30015.mod8;
// import t30015.mod9;
// import t30015.mod10;
// import t30015.mod11;
// import t30015.mod12;
// import t30015.mod13;
// import t30015.mod14;
// import t30015.mod15;
// import t30015.mod16;
// import t30015.mod17;
// import t30015.mod18;
export namespace clanguml::t30015 {
class CBA : public CF {

View File

@@ -30,11 +30,14 @@
void inject_diagram_options(std::shared_ptr<clanguml::config::diagram> diagram)
{
// Inject links config to all test cases
clanguml::config::generate_links_config links_config{
R"(https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }})",
R"({% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"};
clanguml::config::generate_links_config links_config;
diagram->generate_links.set(links_config);
links_config.link.emplace(".",
R"(https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }})");
links_config.tooltip.emplace(".",
R"({% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})");
diagram->generate_links.set(std::move(links_config));
}
std::pair<clanguml::config::config_ptr,
@@ -249,7 +252,8 @@ void try_run_test_case(const diagram_source_storage &diagrams, TC &&tc)
tc(diagrams.get<T>());
}
catch (doctest::TestFailureException &e) {
std::cout << "-----------------------------------------------------"
std::cout << "---------------------------------------------"
"--------"
"--------------------------\n";
std::cout << "Test case failed for diagram type "
<< T::diagram_type_name << ": "
@@ -553,6 +557,10 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
#include "t00077/test_case.h"
#include "t00078/test_case.h"
#include "t00079/test_case.h"
#include "t00080/test_case.h"
#include "t00081/test_case.h"
#include "t00082/test_case.h"
#include "t00083/test_case.h"
///
/// Sequence diagram tests
@@ -619,6 +627,7 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
#include "t20052/test_case.h"
#include "t20053/test_case.h"
#include "t20054/test_case.h"
#include "t20055/test_case.h"
///
/// Package diagram tests

View File

@@ -339,8 +339,16 @@ std::optional<nlohmann::json> get_participant(
if (!j.contains("participants"))
return {};
std::string using_namespace{};
if (j.contains("using_namespace")) {
using_namespace =
fmt::format("{}::", j["using_namespace"].get<std::string>());
}
for (const nlohmann::json &e : j.at("participants")) {
if (e["display_name"] == name)
if (e["display_name"] == name ||
e["full_name"].get<std::string>().substr(using_namespace.size()) ==
name)
return {e};
}
@@ -2482,7 +2490,7 @@ int64_t FindMessage(
if (!fail)
return -1;
std::cout << "FindMessage failed with error " << e.what() << "\n";
std::cout << "FindMessage failed with error: " << e.what() << "\n";
throw e;
}

View File

@@ -234,6 +234,18 @@ test_cases:
- name: t00079
title: Test case for context diagram exclude filter with relationships option
description:
- name: t00080
title: Test case for including elements from system headers
description:
- name: t00081
title: Test case for class members relationships to std types
description:
- name: t00082
title: Test case for advanced diagram filter inclusion test with subclasses and namespaces
description:
- name: t00083
title: Test case for advanced diagram filter exclusion test with subclasses and namespaces
description:
Sequence diagrams:
- name: t20001
title: Basic sequence diagram test case
@@ -397,6 +409,9 @@ test_cases:
- name: t20054
title: Test case for sequence diagram with nested classes
description:
- name: t20055
title: Test case for advanced filter in sequence diagram
description:
Package diagrams:
- name: t30001
title: Basic package diagram test case

View File

@@ -43,10 +43,10 @@ TEST_CASE("Test config simple")
CHECK(diagram.generate_packages() == true);
CHECK(diagram.generate_template_argument_dependencies() == false);
CHECK(diagram.generate_links == true);
CHECK(diagram.generate_links().link ==
CHECK(diagram.generate_links().link.at(".") ==
"https://github.com/bkryza/clang-uml/blob/{{ git.branch }}/{{ "
"element.source.file }}#L{{ element.source.line }}");
CHECK(diagram.generate_links().tooltip == "{{ element.comment }}");
CHECK(diagram.generate_links().tooltip.at(".") == "{{ element.comment }}");
CHECK(
diagram.comment_parser() == clanguml::config::comment_parser_t::clang);

View File

@@ -0,0 +1,87 @@
compilation_database_dir: debug
output_directory: output
filter_mode: advanced
diagrams:
include_test:
type: include
relative_to: ../../../src
glob:
- src/**/*.cc
- src/**/*.h
include:
allof:
paths:
- class_d*/
- common
- util/*.h
- util/*.cc
- main.cc
exclude:
allof:
paths:
- sequence_diagram
- util/error.h
anyof_test:
type: class
relative_to: ../../../src
glob:
- src/**/*.cc
- src/**/*.h
include:
anyof:
namespaces:
- ns1::ns2
elements:
- std::thread
exclude:
anyof:
namespaces:
- ns1::ns2::detail
modules_test:
type: class
include:
anyof:
modules:
- mod1::mod2
namespaces:
- ns1::ns2
method_type_include_test:
type: class
include:
anyof:
namespaces:
- ns1::ns2
method_types:
- constructor
- operator
regex_elements_test:
type: class
include:
elements:
- ns1::ClassA
- r: 'ns1::ns2::Class.+'
- r: 'ns1::.+::ns3::.+'
exclude:
elements:
- ns1::ns2::ClassZ
regex_elements_and_namespaces:
type: class
include:
allof:
elements:
- ns1::ClassA
- r: 'ns1::ns2::Class.+'
- r: 'ns1::.+::ns3::.+'
namespaces:
- r: '.+ns2.+'
edge_filter_and_namespaces:
type: class
filter_mode: advanced
include:
anyof:
subclasses:
- ns1::nsA::A
namespaces:
- ns2::nsB
context:
- ns1::nsA::C

View File

@@ -21,7 +21,7 @@
#include "class_diagram/model/class.h"
#include "cli/cli_handler.h"
#include "common/model/diagram_filter.h"
#include "common/model/filters/diagram_filter_factory.h"
#include "common/model/source_file.h"
#include "config/config.h"
#include "include_diagram/model/diagram.h"
@@ -32,6 +32,7 @@
TEST_CASE("Test diagram paths filter")
{
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::source_file;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -39,7 +40,8 @@ TEST_CASE("Test diagram paths filter")
auto &config = *cfg.diagrams["include_test"];
clanguml::include_diagram::model::diagram diagram;
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
auto make_path = [&](std::string_view p) {
return source_file{config.root_directory() / p};
@@ -59,6 +61,7 @@ TEST_CASE("Test method_types include filter")
using clanguml::class_diagram::model::class_method;
using clanguml::common::model::access_t;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::source_file;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -66,7 +69,8 @@ TEST_CASE("Test method_types include filter")
auto &config = *cfg.diagrams["method_type_include_test"];
clanguml::class_diagram::model::diagram diagram;
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_method cm{access_t::kPublic, "A", ""};
cm.is_constructor(true);
@@ -84,6 +88,7 @@ TEST_CASE("Test method_types exclude filter")
using clanguml::class_diagram::model::class_method;
using clanguml::common::model::access_t;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::source_file;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -91,7 +96,8 @@ TEST_CASE("Test method_types exclude filter")
auto &config = *cfg.diagrams["method_type_exclude_test"];
clanguml::class_diagram::model::diagram diagram;
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_method cm{access_t::kPublic, "A", ""};
@@ -109,22 +115,23 @@ TEST_CASE("Test method_types exclude filter")
TEST_CASE("Test namespaces filter")
{
using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::class_method;
using clanguml::class_diagram::model::class_parent;
using clanguml::common::model::access_t;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::namespace_;
using clanguml::common::model::package;
using clanguml::common::model::source_file;
using clanguml::class_diagram::model::class_;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
auto &config = *cfg.diagrams["namespace_test"];
clanguml::class_diagram::model::diagram diagram;
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_ c{{}};
@@ -173,20 +180,21 @@ TEST_CASE("Test namespaces filter")
TEST_CASE("Test elements regexp filter")
{
using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::class_method;
using clanguml::common::model::access_t;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::namespace_;
using clanguml::common::model::source_file;
using clanguml::class_diagram::model::class_;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
auto &config = *cfg.diagrams["regex_elements_test"];
clanguml::class_diagram::model::diagram diagram;
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_ c{{}};
@@ -213,22 +221,23 @@ TEST_CASE("Test elements regexp filter")
TEST_CASE("Test namespaces regexp filter")
{
using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::class_method;
using clanguml::class_diagram::model::class_parent;
using clanguml::common::model::access_t;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::namespace_;
using clanguml::common::model::package;
using clanguml::common::model::source_file;
using clanguml::class_diagram::model::class_;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
auto &config = *cfg.diagrams["regex_namespace_test"];
clanguml::class_diagram::model::diagram diagram;
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_ c{{}};
@@ -281,8 +290,8 @@ TEST_CASE("Test subclasses regexp filter")
using clanguml::common::model::package;
using clanguml::common::model::source_file;
using namespace std::string_literals;
using clanguml::class_diagram::model::class_;
using clanguml::common::model::diagram_filter_factory;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -353,7 +362,8 @@ TEST_CASE("Test subclasses regexp filter")
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(filter.should_include(*diagram.find<class_>("ns1::ns2::A1")));
CHECK(filter.should_include(*diagram.find<class_>("ns1::ns2::B1")));
@@ -371,8 +381,8 @@ TEST_CASE("Test parents regexp filter")
using clanguml::common::model::package;
using clanguml::common::model::source_file;
using namespace std::string_literals;
using clanguml::class_diagram::model::class_;
using clanguml::common::model::diagram_filter_factory;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -443,7 +453,8 @@ TEST_CASE("Test parents regexp filter")
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(filter.should_include(*diagram.find<class_>("ns1::ns2::BaseA")));
CHECK(filter.should_include(*diagram.find<class_>("ns1::ns2::BaseB")));
@@ -464,8 +475,8 @@ TEST_CASE("Test specializations regexp filter")
using clanguml::common::model::source_file;
using clanguml::common::model::template_parameter;
using namespace std::string_literals;
using clanguml::class_diagram::model::class_;
using clanguml::common::model::diagram_filter_factory;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -508,7 +519,8 @@ TEST_CASE("Test specializations regexp filter")
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(filter.should_include(*diagram.find<class_>("A<int,std::string>")));
CHECK(!filter.should_include(*diagram.find<class_>("A<double>")));
@@ -529,8 +541,8 @@ TEST_CASE("Test context regexp filter")
using clanguml::common::model::source_file;
using clanguml::common::model::template_parameter;
using namespace std::string_literals;
using clanguml::class_diagram::model::class_;
using clanguml::common::model::diagram_filter_factory;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -588,7 +600,8 @@ TEST_CASE("Test context regexp filter")
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(filter.should_include(*diagram.find<class_>("A")));
CHECK(filter.should_include(*diagram.find<class_>("A1")));
@@ -619,6 +632,7 @@ TEST_CASE("Test dependencies regexp filter")
using clanguml::common::model::template_parameter;
using namespace std::string_literals;
using clanguml::class_diagram::model::class_;
using clanguml::common::model::diagram_filter_factory;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -673,7 +687,8 @@ TEST_CASE("Test dependencies regexp filter")
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(filter.should_include(*diagram.find<class_>("A")));
CHECK(!filter.should_include(*diagram.find<class_>("A1")));
@@ -703,6 +718,7 @@ TEST_CASE("Test dependants regexp filter")
using clanguml::common::model::template_parameter;
using namespace std::string_literals;
using clanguml::class_diagram::model::class_;
using clanguml::common::model::diagram_filter_factory;
auto cfg = clanguml::config::load("./test_config_data/filters.yml");
@@ -757,7 +773,8 @@ TEST_CASE("Test dependants regexp filter")
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(filter.should_include(*diagram.find<class_>("A")));
CHECK(filter.should_include(*diagram.find<class_>("A1")));
@@ -775,6 +792,7 @@ TEST_CASE("Test callee_types filter")
{
using clanguml::common::to_id;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::sequence_diagram::model::class_;
using clanguml::sequence_diagram::model::function;
using clanguml::sequence_diagram::model::function_template;
@@ -812,7 +830,8 @@ TEST_CASE("Test callee_types filter")
diagram.add_participant(std::move(p));
diagram.set_complete(true);
diagram_filter filter(diagram, config);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(
filter.should_include(*diagram.get_participant<function>(to_id("A"s))));

View File

@@ -0,0 +1,272 @@
/**
* @file tests/test_filters_advanced.cc
*
* Copyright (c) 2021-2024 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.
*/
#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest/doctest.h"
#include "class_diagram/model/class.h"
#include "cli/cli_handler.h"
#include "common/model/filters/diagram_filter_factory.h"
#include "common/model/source_file.h"
#include "config/config.h"
#include "include_diagram/model/diagram.h"
#include "sequence_diagram/model/diagram.h"
#include <filesystem>
using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::class_method;
using clanguml::common::eid_t;
using clanguml::common::model::access_t;
using clanguml::common::model::diagram_filter;
using clanguml::common::model::diagram_filter_factory;
using clanguml::common::model::namespace_;
using clanguml::common::model::package;
using clanguml::common::model::source_file;
using clanguml::config::filter_mode_t;
TEST_CASE("Test diagram paths filter")
{
auto cfg =
clanguml::config::load("./test_config_data/filters_advanced.yml");
auto &config = *cfg.diagrams["include_test"];
clanguml::include_diagram::model::diagram diagram;
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
auto make_path = [&](std::string_view p) {
return source_file{config.root_directory() / p};
};
CHECK(filter.should_include(
make_path("class_diagram/visitor/translation_unit_visitor.h")));
CHECK(filter.should_include(make_path("main.cc")));
CHECK(filter.should_include(make_path("util/util.cc")));
CHECK_FALSE(filter.should_include(make_path("util/error.h")));
CHECK_FALSE(filter.should_include(
make_path("sequence_diagram/visitor/translation_unit_visitor.h")));
}
TEST_CASE("Test advanced diagram filter anyof")
{
auto cfg =
clanguml::config::load("./test_config_data/filters_advanced.yml");
auto &config = *cfg.diagrams["anyof_test"];
clanguml::include_diagram::model::diagram diagram;
diagram.set_complete(true);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(config.filter_mode() == filter_mode_t::advanced);
CHECK(filter.should_include(namespace_{"ns1::ns2"}));
CHECK_FALSE(filter.should_include(namespace_{"std::string"}));
clanguml::common::model::element std_thread{{}};
std_thread.set_namespace(namespace_{"std"});
std_thread.set_name("thread");
CHECK(filter.should_include(std_thread));
std_thread.set_name("jthread");
CHECK_FALSE(filter.should_include(std_thread));
CHECK_FALSE(filter.should_include(namespace_{"ns1::ns2::detail"}));
}
TEST_CASE("Test advanced diagram filter modules")
{
auto cfg =
clanguml::config::load("./test_config_data/filters_advanced.yml");
auto &config = *cfg.diagrams["modules_test"];
clanguml::include_diagram::model::diagram diagram;
diagram.set_complete(true);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
CHECK(config.filter_mode() == filter_mode_t::advanced);
CHECK(filter.should_include(namespace_{"ns1::ns2"}));
CHECK_FALSE(filter.should_include(namespace_{"std::string"}));
clanguml::common::model::element std_string{{}};
std_string.set_namespace(namespace_{"std"});
std_string.set_name("string");
CHECK_FALSE(filter.should_include(std_string));
CHECK(filter.should_include(namespace_{"ns1"}));
clanguml::common::model::element e1{{}};
e1.set_module("mod1::mod2");
e1.set_namespace(namespace_{"ns5::ns6"});
e1.set_name("ClassA");
CHECK(filter.should_include(e1));
e1.set_module("mod1::mod3");
CHECK_FALSE(filter.should_include(e1));
}
TEST_CASE("Test method_types include filter")
{
auto cfg =
clanguml::config::load("./test_config_data/filters_advanced.yml");
auto &config = *cfg.diagrams["method_type_include_test"];
clanguml::class_diagram::model::diagram diagram;
diagram.set_complete(true);
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_method cm{access_t::kPublic, "A", ""};
cm.is_constructor(true);
CHECK(filter.should_include(cm));
cm.is_constructor(false);
cm.is_destructor(true);
CHECK_FALSE(filter.should_include(cm));
}
TEST_CASE("Test elements and namespaces regexp filter")
{
auto cfg =
clanguml::config::load("./test_config_data/filters_advanced.yml");
auto &config = *cfg.diagrams["regex_elements_and_namespaces"];
clanguml::class_diagram::model::diagram diagram;
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
class_ c{{}};
c.set_namespace(namespace_{"ns1"});
c.set_name("ClassA");
CHECK_FALSE(filter.should_include(c));
c.set_namespace(namespace_{"ns1::ns2"});
c.set_name("ClassA");
CHECK(filter.should_include(c));
c.set_namespace(namespace_{"ns1::ns2"});
c.set_name("ClassZ");
CHECK(filter.should_include(c));
c.set_namespace(namespace_{"ns1::ns5::ns3"});
c.set_name("ClassA");
CHECK_FALSE(filter.should_include(c));
}
TEST_CASE("Test edge filter and namespaces filter")
{
auto cfg =
clanguml::config::load("./test_config_data/filters_advanced.yml");
auto &config = *cfg.diagrams["edge_filter_and_namespaces"];
clanguml::class_diagram::model::diagram diagram;
auto filter_ptr = diagram_filter_factory::create(diagram, config);
diagram_filter &filter = *filter_ptr;
diagram.set_complete(true);
uint64_t id{1};
{
auto ns1 = std::make_unique<package>(namespace_{});
ns1->set_name("ns1");
ns1->set_id(eid_t{id++});
diagram.add(namespace_{}, std::move(ns1));
}
{
auto ns1__nsA = std::make_unique<package>(namespace_{});
ns1__nsA->set_name("nsA");
ns1__nsA->set_namespace(namespace_{"ns1"});
ns1__nsA->set_id(eid_t{id++});
diagram.add(namespace_{}, std::move(ns1__nsA));
}
{
auto A = std::make_unique<class_>(namespace_{});
A->set_namespace(namespace_{"ns1::nsA"});
A->set_name("A");
A->set_id(eid_t{id});
diagram.add(namespace_{"ns1::nsA"}, std::move(A));
}
CHECK(filter.should_include(*diagram.get(eid_t{id})));
class_ c{{}};
c.set_namespace(namespace_{"ns2::nsB"});
c.set_name("B");
CHECK(filter.should_include(c));
{
auto C = std::make_unique<class_>(namespace_{});
C->set_namespace(namespace_{"ns1::nsA"});
C->set_name("C");
C->set_id(eid_t{++id});
diagram.add(namespace_{"ns1::nsA"}, std::move(C));
}
c.set_namespace(namespace_{"ns2::nsB"});
c.set_name("C");
CHECK(filter.should_include(c));
c.set_namespace(namespace_{"ns1::nsA"});
c.set_name("R");
CHECK_FALSE(filter.should_include(c));
}
///
/// Main test function
///
int main(int argc, char *argv[])
{
doctest::Context context;
context.applyCommandLine(argc, argv);
clanguml::cli::cli_handler clih;
std::vector<const char *> argvv = {
"clang-uml", "--config", "./test_config_data/simple.yml"};
argvv.push_back("-q");
clih.handle_options(argvv.size(), argvv.data());
int res = context.run();
if (context.shouldExit())
return res;
return res;
}

View File

@@ -474,4 +474,64 @@ TEST_CASE("Test parse_source_location")
CHECK(column == 456);
result = false, file = "", line = 0, column = 0;
}
TEST_CASE("Test is_subpath")
{
using clanguml::util::is_subpath;
CHECK(is_subpath("./include/f.h", "."));
CHECK(is_subpath("include/f.h", "."));
CHECK(is_subpath("./include/f.h", "./"));
CHECK(is_subpath("./include/f.h", "./include"));
CHECK(is_subpath("./include/f.h", "include"));
CHECK(is_subpath("include/f.h", "./include"));
CHECK(is_subpath("include/f.h", "include"));
CHECK(is_subpath("include/f.h", "include/f.h"));
CHECK(is_subpath("./include/f.h", "include/f.h"));
CHECK(is_subpath("include/f.h", "./include/f.h"));
CHECK(is_subpath("./include/f.h", "include/f.h"));
#if !defined(_MSC_VER)
CHECK(is_subpath("/usr/local/include/f.h", "/usr/local"));
CHECK_FALSE(is_subpath("/usr/local/include/f.h", "/usr/local/lib"));
CHECK_FALSE(is_subpath("/usr/include/f.h", "."));
#else
CHECK(is_subpath("E:\\test\\src\\main.cpp", "E:\\test"));
CHECK_FALSE(is_subpath("E:\\test\\src\\main.cpp", "C:\\test"));
#endif
}
TEST_CASE("Test find_entry_by_path_prefix")
{
using clanguml::util::find_entry_by_path_prefix;
std::map<std::string, std::string> m;
m.emplace(".", "default");
m.emplace("./src", "internal_sources");
m.emplace("./src/thirdparty/", "thirdparty_sources");
m.emplace("/usr/include/boost", "boost_sources");
m.emplace("../my_other_project/src", "my_other_project_sources");
auto kv = find_entry_by_path_prefix(m, "/tmp");
CHECK_FALSE(kv.has_value());
kv = find_entry_by_path_prefix(m, "./src/main.cc");
CHECK(kv.value().second == "internal_sources");
kv = find_entry_by_path_prefix(m, "src/main.cc");
CHECK(kv.value().second == "internal_sources");
kv = find_entry_by_path_prefix(m, "src/thirdparty/main.cc");
CHECK(kv.value().second == "thirdparty_sources");
kv = find_entry_by_path_prefix(m, "include/f.h");
CHECK(kv.value().second == "default");
kv = find_entry_by_path_prefix(m, "./include/f.h");
CHECK(kv.value().second == "default");
kv = find_entry_by_path_prefix(m, "../my_other_project/src/lib.cc");
CHECK(kv.value().second == "my_other_project_sources");
}

View File

@@ -3,7 +3,7 @@ title: Diagram filter visitor class hierarchy
include_relations_also_as_members: true
generate_packages: true
glob:
- src/common/model/diagram_filter.cc
- src/common/model/filters/*.cc
include:
namespaces:
- clanguml