Compare commits
14 Commits
add-addres
...
refactor-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97d42083b | ||
|
|
37314baa3a | ||
|
|
dad583d305 | ||
|
|
a319bd0ede | ||
|
|
d5687907e0 | ||
|
|
077e1c1e70 | ||
|
|
2004d25bdd | ||
|
|
cadbeba82c | ||
|
|
01c6264923 | ||
|
|
4c87c01de2 | ||
|
|
f47d11943e | ||
|
|
fbe3631cff | ||
|
|
40851b395e | ||
|
|
19bb8ae1ca |
@@ -1 +0,0 @@
|
||||
fun:*SmallVector*
|
||||
@@ -70,23 +70,6 @@ if(CODE_COVERAGE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov --coverage")
|
||||
endif(CODE_COVERAGE)
|
||||
|
||||
#
|
||||
# Setup Address Sanitizer
|
||||
#
|
||||
option(ADDRESS_SANITIZER "" OFF)
|
||||
if(ADDRESS_SANITIZER)
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS} \
|
||||
-fno-omit-frame-pointer \
|
||||
-fsanitize=address \
|
||||
-fsanitize-blacklist=${CMAKE_CURRENT_SOURCE_DIR}/.sanitize-blacklist")
|
||||
set(CMAKE_EXE_LINKER_FLAGS
|
||||
"${CMAKE_EXE_LINKER_FLAGS} \
|
||||
-fno-omit-frame-pointer \
|
||||
-fsanitize=address \
|
||||
-fsanitize-blacklist=${CMAKE_CURRENT_SOURCE_DIR}/.sanitize-blacklist")
|
||||
endif(ADDRESS_SANITIZER)
|
||||
|
||||
#
|
||||
# Setup LLVM
|
||||
#
|
||||
|
||||
3
Makefile
3
Makefile
@@ -38,7 +38,6 @@ CMAKE_CXX_FLAGS ?=
|
||||
CMAKE_EXE_LINKER_FLAGS ?=
|
||||
CMAKE_GENERATOR ?= Unix Makefiles
|
||||
CODE_COVERAGE ?= OFF
|
||||
ADDRESS_SANITIZER ?= OFF
|
||||
|
||||
ENABLE_CXX_MODULES_TEST_CASES ?= OFF
|
||||
ENABLE_CUDA_TEST_CASES ?= OFF
|
||||
@@ -70,7 +69,6 @@ debug/CMakeLists.txt:
|
||||
-DENABLE_CUDA_TEST_CASES=$(ENABLE_CUDA_TEST_CASES) \
|
||||
-DENABLE_CXX_MODULES_TEST_CASES=$(ENABLE_CXX_MODULES_TEST_CASES) \
|
||||
-DCODE_COVERAGE=$(CODE_COVERAGE) \
|
||||
-DADDRESS_SANITIZER=$(ADDRESS_SANITIZER) \
|
||||
-DCLANG_UML_ENABLE_BACKTRACE=$(CLANG_UML_ENABLE_BACKTRACE)
|
||||
|
||||
release/CMakeLists.txt:
|
||||
@@ -86,7 +84,6 @@ release/CMakeLists.txt:
|
||||
-DLLVM_CONFIG_PATH=${LLVM_CONFIG_PATH} \
|
||||
-DLINK_LLVM_SHARED=${LLVM_SHARED} \
|
||||
-DCMAKE_PREFIX=${CMAKE_PREFIX} \
|
||||
-DADDRESS_SANITIZER=$(ADDRESS_SANITIZER) \
|
||||
-DENABLE_CUDA_TEST_CASES=$(ENABLE_CUDA_TEST_CASES) \
|
||||
-DENABLE_CXX_MODULES_TEST_CASES=$(ENABLE_CXX_MODULES_TEST_CASES) \
|
||||
-DCLANG_UML_ENABLE_BACKTRACE=$(CLANG_UML_ENABLE_BACKTRACE)
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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() &&
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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, ", "));
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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, "<", "<");
|
||||
util::replace_all(name, ">", ">");
|
||||
@@ -86,7 +93,6 @@ std::string render_name(std::string name, bool round_brackets)
|
||||
util::replace_all(name, "(", "(");
|
||||
util::replace_all(name, ")", ")");
|
||||
}
|
||||
util::replace_all(name, "##", "::");
|
||||
util::replace_all(name, "{", "{");
|
||||
util::replace_all(name, "}", "}");
|
||||
|
||||
|
||||
@@ -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, "\"", "„");
|
||||
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, "\"", "„");
|
||||
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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
366
src/common/model/filters/diagram_filter_factory.cc
Normal file
366
src/common/model/filters/diagram_filter_factory.cc
Normal 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
|
||||
126
src/common/model/filters/diagram_filter_factory.h
Normal file
126
src/common/model/filters/diagram_filter_factory.h
Normal 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
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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()});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
#include "diagram.h"
|
||||
|
||||
#include "common/model/diagram_filter.h"
|
||||
#include "common/model/filters/diagram_filter.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
15
tests/t00080/.clang-uml
Normal 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
24
tests/t00080/t00080.cc
Normal 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
35
tests/t00080/test_case.h
Normal 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
25
tests/t00081/.clang-uml
Normal 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
18
tests/t00081/t00081.cc
Normal 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
35
tests/t00081/test_case.h
Normal 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
20
tests/t00082/.clang-uml
Normal 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
30
tests/t00082/t00082.cc
Normal 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
46
tests/t00082/test_case.h
Normal 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
16
tests/t00083/.clang-uml
Normal 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
30
tests/t00083/t00083.cc
Normal 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
46
tests/t00083/test_case.h
Normal 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"}));
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t20002 {
|
||||
|
||||
|
||||
16
tests/t20055/.clang-uml
Normal file
16
tests/t20055/.clang-uml
Normal 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
40
tests/t20055/t20055.cc
Normal 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
40
tests/t20055/test_case.h
Normal 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()"}));
|
||||
});
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
87
tests/test_config_data/filters_advanced.yml
Normal file
87
tests/test_config_data/filters_advanced.yml
Normal 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
|
||||
@@ -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))));
|
||||
|
||||
272
tests/test_filters_advanced.cc
Normal file
272
tests/test_filters_advanced.cc
Normal 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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user