Added relationship direction flag to context diagram filter (#274)

This commit is contained in:
Bartek Kryza
2024-06-13 14:19:45 +02:00
parent 7d859db15a
commit bd921822c2
11 changed files with 240 additions and 35 deletions

View File

@@ -659,10 +659,10 @@ void context_filter::initialize_effective_context(
auto &effective_context = effective_contexts_[idx]; auto &effective_context = effective_contexts_[idx];
// First add to effective context all elements matching context_ patterns // First add to effective context all elements matching context_ patterns
const auto &context = context_.at(idx); const auto &context_cfg = context_.at(idx);
const auto &context_matches = const auto &context_matches =
dynamic_cast<const class_diagram::model::diagram &>(d) dynamic_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::class_>(context.pattern); .find<class_diagram::model::class_>(context_cfg.pattern);
for (const auto &maybe_match : context_matches) { for (const auto &maybe_match : context_matches) {
if (maybe_match) if (maybe_match)
@@ -671,7 +671,7 @@ void context_filter::initialize_effective_context(
const auto &context_enum_matches = const auto &context_enum_matches =
dynamic_cast<const class_diagram::model::diagram &>(d) dynamic_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::enum_>(context.pattern); .find<class_diagram::model::enum_>(context_cfg.pattern);
for (const auto &maybe_match : context_enum_matches) { for (const auto &maybe_match : context_enum_matches) {
if (maybe_match) if (maybe_match)
@@ -680,7 +680,7 @@ void context_filter::initialize_effective_context(
const auto &context_concept_matches = const auto &context_concept_matches =
dynamic_cast<const class_diagram::model::diagram &>(d) dynamic_cast<const class_diagram::model::diagram &>(d)
.find<class_diagram::model::concept_>(context.pattern); .find<class_diagram::model::concept_>(context_cfg.pattern);
for (const auto &maybe_match : context_concept_matches) { for (const auto &maybe_match : context_concept_matches) {
if (maybe_match) if (maybe_match)
@@ -689,7 +689,7 @@ void context_filter::initialize_effective_context(
// Now repeat radius times - extend the effective context with elements // Now repeat radius times - extend the effective context with elements
// matching in direct relationship to what is in context // matching in direct relationship to what is in context
auto radius_counter = context.radius; auto radius_counter = context_cfg.radius;
std::set<eid_t> current_iteration_context; std::set<eid_t> current_iteration_context;
while (radius_counter > 0 && effective_context_extended) { while (radius_counter > 0 && effective_context_extended) {
@@ -701,18 +701,18 @@ void context_filter::initialize_effective_context(
// For each class in the model // For each class in the model
find_elements_in_direct_relationship<class_diagram::model::class_>( find_elements_in_direct_relationship<class_diagram::model::class_>(
d, effective_context, current_iteration_context); d, context_cfg, effective_context, current_iteration_context);
find_elements_inheritance_relationship( find_elements_inheritance_relationship(
d, effective_context, current_iteration_context); d, context_cfg, effective_context, current_iteration_context);
// For each concept in the model // For each concept in the model
find_elements_in_direct_relationship<class_diagram::model::concept_>( find_elements_in_direct_relationship<class_diagram::model::concept_>(
d, effective_context, current_iteration_context); d, context_cfg, effective_context, current_iteration_context);
// For each enum in the model // For each enum in the model
find_elements_in_direct_relationship<class_diagram::model::enum_>( find_elements_in_direct_relationship<class_diagram::model::enum_>(
d, effective_context, current_iteration_context); d, context_cfg, effective_context, current_iteration_context);
for (auto id : current_iteration_context) { for (auto id : current_iteration_context) {
if (effective_context.count(id) == 0) { if (effective_context.count(id) == 0) {
@@ -725,6 +725,7 @@ void context_filter::initialize_effective_context(
} }
void context_filter::find_elements_inheritance_relationship(const diagram &d, void context_filter::find_elements_inheritance_relationship(const diagram &d,
const config::context_config &context_cfg,
std::set<eid_t> &effective_context, std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context) const std::set<eid_t> &current_iteration_context) const
{ {
@@ -733,20 +734,23 @@ void context_filter::find_elements_inheritance_relationship(const diagram &d,
for (const auto &c : cd.classes()) { for (const auto &c : cd.classes()) {
// Check if any of the elements parents are already in the // Check if any of the elements parents are already in the
// effective context... // effective context...
for (const class_diagram::model::class_parent &p : c.get().parents()) { if (context_cfg.direction != config::context_direction_t::outward)
for (const auto &ec : effective_context) { find_elements_base_classes(
const auto &maybe_parent = d, effective_context, current_iteration_context, cd, c);
cd.find<class_diagram::model::class_>(ec);
if (!maybe_parent)
continue;
if (d.should_include(relationship_t::kExtension) &&
maybe_parent.value().full_name(false) == p.name())
current_iteration_context.emplace(c.get().id());
}
}
// .. or vice-versa // .. or vice-versa
if (context_cfg.direction != config::context_direction_t::inward)
find_elements_sub_classes(
effective_context, current_iteration_context, cd, c);
}
}
void context_filter::find_elements_sub_classes(
std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context,
const class_diagram::model::diagram &cd,
const std::reference_wrapper<class_diagram::model::class_> &c) const
{
for (const auto &ec : effective_context) { for (const auto &ec : effective_context) {
const auto &maybe_child = cd.find<class_diagram::model::class_>(ec); const auto &maybe_child = cd.find<class_diagram::model::class_>(ec);
@@ -761,6 +765,24 @@ void context_filter::find_elements_inheritance_relationship(const diagram &d,
} }
} }
} }
}
void context_filter::find_elements_base_classes(const diagram &d,
std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context,
const class_diagram::model::diagram &cd,
const std::reference_wrapper<class_diagram::model::class_> &c) const
{
for (const class_diagram::model::class_parent &p : c.get().parents()) {
for (const auto &ec : effective_context) {
const auto &maybe_parent =
cd.find<class_diagram::model::class_>(ec);
if (!maybe_parent)
continue;
if (d.should_include(relationship_t::kExtension) &&
maybe_parent.value().full_name(false) == p.name())
current_iteration_context.emplace(c.get().id());
}
} }
} }
@@ -802,6 +824,16 @@ tvl::value_t context_filter::match(const diagram &d, const element &e) const
return false; return false;
} }
bool context_filter::is_inward(relationship_t r) const
{
return r == relationship_t::kAssociation;
}
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, paths_filter::paths_filter(filter_t type, const std::filesystem::path &root,
const std::vector<std::string> &p) const std::vector<std::string> &p)
: filter_visitor{type} : filter_visitor{type}

View File

@@ -494,8 +494,13 @@ private:
void initialize_effective_context(const diagram &d, unsigned idx) const; void initialize_effective_context(const diagram &d, unsigned idx) const;
bool is_inward(relationship_t r) const;
bool is_outward(relationship_t r) const;
template <typename ElementT> template <typename ElementT>
void find_elements_in_direct_relationship(const diagram &d, void find_elements_in_direct_relationship(const diagram &d,
const config::context_config &context_cfg,
std::set<eid_t> &effective_context, std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context) const std::set<eid_t> &current_iteration_context) const
{ {
@@ -511,6 +516,12 @@ private:
// which have a relationship to any of the effective_context // which have a relationship to any of the effective_context
// elements // elements
for (const relationship &rel : el.get().relationships()) { for (const relationship &rel : el.get().relationships()) {
if (context_cfg.direction ==
config::context_direction_t::outward/* &&
!is_inward(rel.type())*/) {
continue;
}
for (const auto &element_id : effective_context) { for (const auto &element_id : effective_context) {
if (d.should_include(rel.type()) && if (d.should_include(rel.type()) &&
rel.destination() == element_id) rel.destination() == element_id)
@@ -519,7 +530,9 @@ private:
} }
// Now search current effective_context elements and add any // Now search current effective_context elements and add any
// elements of any type in the diagram which to that element // elements of any type in the diagram which have a relationship
// to that element
for (const auto element_id : effective_context) { for (const auto element_id : effective_context) {
const auto &maybe_element = cd.get(element_id); const auto &maybe_element = cd.get(element_id);
@@ -528,6 +541,12 @@ private:
for (const relationship &rel : for (const relationship &rel :
maybe_element.value().relationships()) { maybe_element.value().relationships()) {
if ((context_cfg.direction ==
config::context_direction_t::inward) &&
(rel.type() != relationship_t::kAggregation &&
rel.type() != relationship_t::kComposition)) {
continue;
}
if (d.should_include(rel.type()) && if (d.should_include(rel.type()) &&
rel.destination() == el.get().id()) rel.destination() == el.get().id())
@@ -538,9 +557,21 @@ private:
} }
void find_elements_inheritance_relationship(const diagram &d, void find_elements_inheritance_relationship(const diagram &d,
const config::context_config &context_cfg,
std::set<eid_t> &effective_context, std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context) const; std::set<eid_t> &current_iteration_context) const;
void find_elements_base_classes(const diagram &d,
std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context,
const class_diagram::model::diagram &cd,
const std::reference_wrapper<class_diagram::model::class_> &c) const;
void find_elements_sub_classes(std::set<eid_t> &effective_context,
std::set<eid_t> &current_iteration_context,
const class_diagram::model::diagram &cd,
const std::reference_wrapper<class_diagram::model::class_> &c) const;
std::vector<config::context_config> context_; std::vector<config::context_config> context_;
/*! /*!

View File

@@ -171,6 +171,20 @@ std::string to_string(member_order_t mo)
return ""; return "";
} }
} }
std::string to_string(context_direction_t cd)
{
switch (cd) {
case context_direction_t::inward:
return "inward";
case context_direction_t::outward:
return "outward";
case context_direction_t::any:
return "any";
default:
assert(false);
return "";
}
}
std::optional<std::string> plantuml::get_style( std::optional<std::string> plantuml::get_style(
const common::model::relationship_t relationship_type) const const common::model::relationship_t relationship_type) const

View File

@@ -159,9 +159,14 @@ struct mermaid {
void append(const mermaid &r); void append(const mermaid &r);
}; };
enum class context_direction_t { inward, outward, any };
std::string to_string(context_direction_t cd);
struct context_config { struct context_config {
common::string_or_regex pattern; common::string_or_regex pattern;
unsigned radius{0}; unsigned radius{0};
context_direction_t direction{context_direction_t::any};
}; };
/** /**

View File

@@ -114,10 +114,15 @@ types:
- lambda - lambda
- cuda_kernel - cuda_kernel
- cuda_device - cuda_device
direction_t: !variant
- inward
- outward
- any
context_filter_match_t: context_filter_match_t:
match: match:
radius: int radius: int
pattern: regex_or_string_t pattern: regex_or_string_t
direction: !optional direction_t
context_filter_t: context_filter_t:
- regex_or_string_t - regex_or_string_t
- context_filter_match_t - context_filter_match_t

View File

@@ -35,6 +35,7 @@ using clanguml::config::callee_type;
using clanguml::config::class_diagram; using clanguml::config::class_diagram;
using clanguml::config::config; using clanguml::config::config;
using clanguml::config::context_config; using clanguml::config::context_config;
using clanguml::config::context_direction_t;
using clanguml::config::diagram_template; using clanguml::config::diagram_template;
using clanguml::config::filter; using clanguml::config::filter;
using clanguml::config::generate_links_config; using clanguml::config::generate_links_config;
@@ -259,6 +260,25 @@ template <> struct convert<module_access_t> {
} }
}; };
//
// config context_direction_t decoder
//
template <> struct convert<context_direction_t> {
static bool decode(const Node &node, context_direction_t &rhs)
{
if (node.as<std::string>() == "inward")
rhs = context_direction_t::inward;
else if (node.as<std::string>() == "outward")
rhs = context_direction_t::outward;
else if (node.as<std::string>() == "any")
rhs = context_direction_t::any;
else
return false;
return true;
}
};
// //
// config method_type decoder // config method_type decoder
// //
@@ -460,8 +480,11 @@ template <> struct convert<context_config> {
{ {
using namespace std::string_literals; using namespace std::string_literals;
if (node.IsMap() && has_key(node, "match")) { if (node.IsMap() && has_key(node, "match")) {
rhs.radius = node["match"]["radius"].as<unsigned>(); const auto &match = node["match"];
rhs.pattern = node["match"]["pattern"].as<string_or_regex>(); rhs.radius = match["radius"].as<unsigned>();
rhs.pattern = match["pattern"].as<string_or_regex>();
if (has_key(match, "direction"))
rhs.direction = match["direction"].as<context_direction_t>();
} }
else { else {
rhs.radius = 1; rhs.radius = 1;

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

@@ -0,0 +1,16 @@
diagrams:
t00076_class:
type: class
glob:
- t00076.cc
include:
namespaces:
- clanguml::t00076
context:
- match:
radius: 1
pattern: clanguml::t00076::B
direction: inward
#relationships:
# - any
using_namespace: clanguml::t00076

36
tests/t00076/t00076.cc Normal file
View File

@@ -0,0 +1,36 @@
namespace clanguml {
namespace t00076 {
enum Color { red, green, blue };
struct F;
struct G { };
struct H { };
struct J { };
struct A { };
struct B : public A {
F *f;
Color c;
G g;
/// @uml{composition[0..1:1..*]}
J j;
void a(H *h) { (void)h; }
};
struct C : public B { };
struct D : public C { };
struct E {
B *b;
};
struct F { };
struct I {
void i(B *b) { (void)b; }
};
}
}

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

@@ -0,0 +1,40 @@
/**
* tests/t00076/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("t00076")
{
using namespace clanguml::test;
using namespace std::string_literals;
auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00076", "t00076_class");
CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(IsClass(src, "B"));
REQUIRE(IsClass(src, "C"));
REQUIRE(IsClass(src, "E"));
REQUIRE(IsClass(src, "G"));
REQUIRE(IsClass(src, "I"));
REQUIRE(IsClass(src, "J"));
REQUIRE(IsEnum(src, "Color"));
REQUIRE(!IsClass(src, "A"));
REQUIRE(!IsClass(src, "D"));
REQUIRE(!IsClass(src, "F"));
REQUIRE(!IsClass(src, "H"));
});
}

View File

@@ -549,6 +549,7 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
#include "t00074/test_case.h" #include "t00074/test_case.h"
#include "t00075/test_case.h" #include "t00075/test_case.h"
#endif #endif
#include "t00076/test_case.h"
/// ///
/// Sequence diagram tests /// Sequence diagram tests
@@ -664,7 +665,7 @@ int main(int argc, char *argv[])
std::vector<const char *> argvv = { std::vector<const char *> argvv = {
"clang-uml", "--config", "./test_config_data/simple.yml"}; "clang-uml", "--config", "./test_config_data/simple.yml"};
argvv.push_back("-q"); argvv.push_back("-vvv");
clih.handle_options(argvv.size(), argvv.data()); clih.handle_options(argvv.size(), argvv.data());

View File

@@ -222,6 +222,8 @@ test_cases:
- name: t00075 - name: t00075
title: Test case for class diagram styles in config file title: Test case for class diagram styles in config file
description: description:
- name: t00076
title: Test case for context diagram with inward direction flag
Sequence diagrams: Sequence diagrams:
- name: t20001 - name: t20001
title: Basic sequence diagram test case title: Basic sequence diagram test case