Added module_access diagram filter (#101)

This commit is contained in:
Bartek Kryza
2023-12-19 22:16:18 +01:00
parent c51ae5b6ee
commit f09edd8b47
16 changed files with 189 additions and 5 deletions

View File

@@ -23,6 +23,14 @@
namespace clanguml::class_diagram::model { namespace clanguml::class_diagram::model {
using nlohmann::json; using nlohmann::json;
void set_module(nlohmann::json &j, const common::model::element &e)
{
if (e.module()) {
j["module"]["name"] = e.module().value();
j["module"]["is_private"] = e.module_private();
}
}
void to_json(nlohmann::json &j, const class_element &c) void to_json(nlohmann::json &j, const class_element &c)
{ {
j["name"] = c.name(); j["name"] = c.name();
@@ -94,6 +102,8 @@ void to_json(nlohmann::json &j, const class_ &c)
j["methods"] = c.methods(); j["methods"] = c.methods();
j["bases"] = c.parents(); j["bases"] = c.parents();
set_module(j, c);
j["template_parameters"] = c.template_params(); j["template_parameters"] = c.template_params();
} }
@@ -102,6 +112,8 @@ void to_json(nlohmann::json &j, const enum_ &c)
j = dynamic_cast<const common::model::element &>(c); j = dynamic_cast<const common::model::element &>(c);
j["is_nested"] = c.is_nested(); j["is_nested"] = c.is_nested();
j["constants"] = c.constants(); j["constants"] = c.constants();
set_module(j, c);
} }
void to_json(nlohmann::json &j, const concept_ &c) void to_json(nlohmann::json &j, const concept_ &c)
@@ -109,6 +121,8 @@ void to_json(nlohmann::json &j, const concept_ &c)
j = dynamic_cast<const common::model::element &>(c); j = dynamic_cast<const common::model::element &>(c);
j["parameters"] = c.requires_parameters(); j["parameters"] = c.requires_parameters();
j["statements"] = c.requires_statements(); j["statements"] = c.requires_statements();
set_module(j, c);
} }
} // namespace clanguml::class_diagram::model } // namespace clanguml::class_diagram::model

View File

@@ -568,6 +568,31 @@ tvl::value_t access_filter::match(
[&a](const auto &access) { return a == access; }); [&a](const auto &access) { return a == access; });
} }
module_access_filter::module_access_filter(
filter_t type, std::vector<module_access_t> access)
: filter_visitor{type}
, access_{std::move(access)}
{
}
tvl::value_t module_access_filter::match(
const diagram & /*d*/, const element &e) const
{
if (!e.module().has_value())
return {};
if (access_.empty())
return {};
return tvl::any_of(
access_.begin(), access_.end(), [&e](const auto &access) {
if (access == module_access_t::kPublic)
return !e.module_private();
else
return e.module_private();
});
}
context_filter::context_filter( context_filter::context_filter(
filter_t type, std::vector<config::context_config> context) filter_t type, std::vector<config::context_config> context)
: filter_visitor{type} : filter_visitor{type}
@@ -924,6 +949,9 @@ void diagram_filter::init_filters(const config::diagram &c)
add_inclusive_filter(std::make_unique<modules_filter>( add_inclusive_filter(std::make_unique<modules_filter>(
filter_t::kInclusive, c.include().modules)); 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>( add_inclusive_filter(std::make_unique<relationship_filter>(
filter_t::kInclusive, c.include().relationships)); filter_t::kInclusive, c.include().relationships));
@@ -1037,6 +1065,9 @@ void diagram_filter::init_filters(const config::diagram &c)
add_exclusive_filter(std::make_unique<modules_filter>( add_exclusive_filter(std::make_unique<modules_filter>(
filter_t::kExclusive, c.exclude().modules)); 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>( add_exclusive_filter(std::make_unique<paths_filter>(
filter_t::kExclusive, c.root_directory(), c.exclude().paths)); filter_t::kExclusive, c.root_directory(), c.exclude().paths));

View File

@@ -457,6 +457,20 @@ private:
std::vector<access_t> access_; std::vector<access_t> access_;
}; };
/**
* Match diagram elements based on module access (public or private).
*/
struct module_access_filter : public filter_visitor {
module_access_filter(filter_t type, std::vector<module_access_t> access);
~module_access_filter() override = default;
tvl::value_t match(const diagram &d, const element &a) const override;
private:
std::vector<module_access_t> access_;
};
/** /**
* Match diagram elements which are in within a 'radius' distance relationship * Match diagram elements which are in within a 'radius' distance relationship
* to any of the elements specified in context. * to any of the elements specified in context.

View File

@@ -101,6 +101,23 @@ public:
*/ */
std::optional<std::string> module() const { return module_; } std::optional<std::string> module() const { return module_; }
/**
* Set whether the element is in a private module
*
* @param module C++20 module.
*/
void set_module_private(const bool module_private)
{
module_private_ = module_private;
}
/**
* Check whether the element is in a private module.
*
* @return C++20 module.
*/
bool module_private() const { return module_private_; }
/** /**
* Return elements full name. * Return elements full name.
* *
@@ -135,5 +152,6 @@ private:
namespace_ ns_; namespace_ ns_;
namespace_ using_namespace_; namespace_ using_namespace_;
std::optional<std::string> module_; std::optional<std::string> module_;
bool module_private_{false};
}; };
} // namespace clanguml::common::model } // namespace clanguml::common::model

View File

@@ -70,6 +70,19 @@ std::string to_string(access_t a)
} }
} }
std::string to_string(module_access_t a)
{
switch (a) {
case module_access_t::kPublic:
return "public";
case module_access_t::kPrivate:
return "private";
default:
assert(false);
return "";
}
}
std::string to_string(message_t r) std::string to_string(message_t r)
{ {
switch (r) { switch (r) {

View File

@@ -23,6 +23,7 @@ namespace clanguml::common::model {
enum class diagram_t { kClass, kSequence, kPackage, kInclude }; enum class diagram_t { kClass, kSequence, kPackage, kInclude };
enum class module_access_t { kPublic, kPrivate };
enum class access_t { kPublic, kProtected, kPrivate, kNone }; enum class access_t { kPublic, kProtected, kPrivate, kNone };
enum class relationship_t { enum class relationship_t {
@@ -77,6 +78,8 @@ std::string to_string(relationship_t r);
std::string to_string(access_t r); std::string to_string(access_t r);
std::string to_string(module_access_t r);
std::string to_string(message_t m); std::string to_string(message_t m);
std::string to_string(diagram_t r); std::string to_string(diagram_t r);

View File

@@ -168,7 +168,13 @@ void translation_unit_visitor::set_owning_module(
{ {
if (const clang::Module *module = decl.getOwningModule(); if (const clang::Module *module = decl.getOwningModule();
module != nullptr) { module != nullptr) {
element.set_module(module->Name); std::string module_name = module->Name;
if (module->isPrivateModule()) {
// Clang just maps private modules names to "<private>"
module_name = module->getTopLevelModule()->Name;
}
element.set_module(module_name);
element.set_module_private(module->isPrivateModule());
} }
} }
} // namespace clanguml::common::visitor } // namespace clanguml::common::visitor

View File

@@ -194,6 +194,22 @@ struct filter {
*/ */
std::vector<common::string_or_regex> modules; std::vector<common::string_or_regex> modules;
/*! @brief Access type filter
*
* This filter allows to filter class members methods based on their access:
* - public
* - private
*
* Example:
*
* ```yaml
* include:
* module_access:
* - public
* ```
*/
std::vector<common::model::module_access_t> module_access;
/*! @brief Elements filter /*! @brief Elements filter
* *
* Example: * Example:
@@ -245,8 +261,8 @@ struct filter {
* *
* ```yaml * ```yaml
* include: * include:
* relationships: * access:
* - inheritance * - public
* ``` * ```
*/ */
std::vector<common::model::access_t> access; std::vector<common::model::access_t> access;

View File

@@ -91,6 +91,9 @@ types:
- public - public
- protected - protected
- private - private
module_access_filter_t: !variant
- public
- private
method_type_filter_t: !variant method_type_filter_t: !variant
- constructor - constructor
- destructor - destructor
@@ -123,6 +126,7 @@ types:
element_types: !optional [element_types_filter_t] element_types: !optional [element_types_filter_t]
relationships: !optional [relationship_filter_t] relationships: !optional [relationship_filter_t]
access: !optional [access_filter_t] access: !optional [access_filter_t]
module_access: !optional [module_access_filter_t]
subclasses: !optional [regex_or_string_t] subclasses: !optional [regex_or_string_t]
parents: !optional [regex_or_string_t] parents: !optional [regex_or_string_t]
specializations: !optional [regex_or_string_t] specializations: !optional [regex_or_string_t]

View File

@@ -29,6 +29,7 @@ namespace YAML {
using clanguml::common::namespace_or_regex; using clanguml::common::namespace_or_regex;
using clanguml::common::string_or_regex; using clanguml::common::string_or_regex;
using clanguml::common::model::access_t; using clanguml::common::model::access_t;
using clanguml::common::model::module_access_t;
using clanguml::common::model::relationship_t; using clanguml::common::model::relationship_t;
using clanguml::config::callee_type; using clanguml::config::callee_type;
using clanguml::config::class_diagram; using clanguml::config::class_diagram;
@@ -241,6 +242,23 @@ template <> struct convert<access_t> {
} }
}; };
//
// config module_access_t decoder
//
template <> struct convert<module_access_t> {
static bool decode(const Node &node, module_access_t &rhs)
{
if (node.as<std::string>() == "public")
rhs = module_access_t::kPublic;
else if (node.as<std::string>() == "private")
rhs = module_access_t::kPrivate;
else
return false;
return true;
}
};
// //
// config method_type decoder // config method_type decoder
// //
@@ -483,6 +501,10 @@ template <> struct convert<filter> {
rhs.modules.push_back({ns}); rhs.modules.push_back({ns});
} }
if (node["module_access"])
rhs.module_access =
node["module_access"].as<decltype(rhs.module_access)>();
if (node["relationships"]) if (node["relationships"])
rhs.relationships = rhs.relationships =
node["relationships"].as<decltype(rhs.relationships)>(); node["relationships"].as<decltype(rhs.relationships)>();

View File

@@ -69,6 +69,12 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const access_t &a)
return out; return out;
} }
YAML::Emitter &operator<<(YAML::Emitter &out, const module_access_t &a)
{
out << to_string(a);
return out;
}
YAML::Emitter &operator<<(YAML::Emitter &out, const diagram_t &d) YAML::Emitter &operator<<(YAML::Emitter &out, const diagram_t &d)
{ {
out << to_string(d); out << to_string(d);
@@ -124,6 +130,8 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const filter &f)
out << YAML::Key << "namespaces" << YAML::Value << f.namespaces; out << YAML::Key << "namespaces" << YAML::Value << f.namespaces;
if (!f.modules.empty()) if (!f.modules.empty())
out << YAML::Key << "modules" << YAML::Value << f.modules; out << YAML::Key << "modules" << YAML::Value << f.modules;
if (!f.module_access.empty())
out << YAML::Key << "module_access" << YAML::Value << f.module_access;
if (!f.access.empty()) if (!f.access.empty())
out << YAML::Key << "access" << YAML::Value << f.access; out << YAML::Key << "access" << YAML::Value << f.access;
if (!f.context.empty()) if (!f.context.empty())

View File

@@ -9,4 +9,6 @@ diagrams:
exclude: exclude:
modules: modules:
- t00070.lib2 - t00070.lib2
module_access:
- private
using_namespace: clanguml::t00070 using_namespace: clanguml::t00070

View File

@@ -9,3 +9,8 @@ template <typename T> class BB {
enum class BBB { bbb1, bbb2 }; enum class BBB { bbb1, bbb2 };
} }
module :private;
namespace clanguml::t00070 {
class BBBB { };
}

View File

@@ -1,3 +1,4 @@
import t00070;
import t00070.lib1; import t00070.lib1;
import t00070.lib2; import t00070.lib2;

View File

@@ -35,7 +35,7 @@ TEST_CASE("t00070", "[test-case][class]")
REQUIRE_THAT(src, StartsWith("@startuml")); REQUIRE_THAT(src, StartsWith("@startuml"));
REQUIRE_THAT(src, EndsWith("@enduml\n")); REQUIRE_THAT(src, EndsWith("@enduml\n"));
REQUIRE_THAT(src, !IsClass(_A("A"))); REQUIRE_THAT(src, IsClass(_A("A")));
REQUIRE_THAT(src, IsClass(_A("B"))); REQUIRE_THAT(src, IsClass(_A("B")));
REQUIRE_THAT(src, !IsClass(_A("C"))); REQUIRE_THAT(src, !IsClass(_A("C")));
@@ -43,6 +43,7 @@ TEST_CASE("t00070", "[test-case][class]")
REQUIRE_THAT(src, !IsClassTemplate("CC", "T")); REQUIRE_THAT(src, !IsClassTemplate("CC", "T"));
REQUIRE_THAT(src, IsEnum(_A("BBB"))); REQUIRE_THAT(src, IsEnum(_A("BBB")));
REQUIRE_THAT(src, !IsClass(_A("BBBB")));
REQUIRE_THAT(src, !IsEnum(_A("CCC"))); REQUIRE_THAT(src, !IsEnum(_A("CCC")));
save_puml(config.output_directory(), diagram->name + ".puml", src); save_puml(config.output_directory(), diagram->name + ".puml", src);
@@ -53,6 +54,15 @@ TEST_CASE("t00070", "[test-case][class]")
using namespace json; using namespace json;
REQUIRE(IsClass(j, "A"));
REQUIRE(IsClass(j, "B"));
REQUIRE(!IsClass(j, "C"));
REQUIRE(InPublicModule(j, "A", "t00070"));
REQUIRE(InPublicModule(j, "B", "t00070.lib1"));
REQUIRE(!IsClass(j, "BBBB"));
save_json(config.output_directory(), diagram->name + ".json", j); save_json(config.output_directory(), diagram->name + ".json", j);
} }
@@ -63,7 +73,7 @@ TEST_CASE("t00070", "[test-case][class]")
using mermaid::IsClass; using mermaid::IsClass;
using mermaid::IsEnum; using mermaid::IsEnum;
REQUIRE_THAT(src, !IsClass(_A("A"))); REQUIRE_THAT(src, IsClass(_A("A")));
REQUIRE_THAT(src, IsClass(_A("B"))); REQUIRE_THAT(src, IsClass(_A("B")));
REQUIRE_THAT(src, !IsClass(_A("C"))); REQUIRE_THAT(src, !IsClass(_A("C")));
@@ -71,6 +81,7 @@ TEST_CASE("t00070", "[test-case][class]")
REQUIRE_THAT(src, !IsClass(_A("CC<T>"))); REQUIRE_THAT(src, !IsClass(_A("CC<T>")));
REQUIRE_THAT(src, IsEnum(_A("BBB"))); REQUIRE_THAT(src, IsEnum(_A("BBB")));
REQUIRE_THAT(src, !IsClass(_A("BBBB")));
REQUIRE_THAT(src, !IsEnum(_A("CCC"))); REQUIRE_THAT(src, !IsEnum(_A("CCC")));
save_mermaid(config.output_directory(), diagram->name + ".mmd", src); save_mermaid(config.output_directory(), diagram->name + ".mmd", src);

View File

@@ -1278,6 +1278,22 @@ bool IsClass(const nlohmann::json &j, const std::string &name)
return e && e->at("type") == "class"; return e && e->at("type") == "class";
} }
bool InPublicModule(const nlohmann::json &j, const std::string &element,
const std::string &module)
{
auto e = get_element(j, expand_name(j, element));
return e && e->contains("module") && e->at("module")["name"] == module &&
!e->at("module")["is_private"];
}
bool InPrivateModule(const nlohmann::json &j, const std::string &element,
const std::string &module)
{
auto e = get_element(j, expand_name(j, element));
return e && e->contains("module") && e->at("module")["name"] == module &&
e->at("module")["is_private"];
}
bool IsAbstractClass(const nlohmann::json &j, const std::string &name) bool IsAbstractClass(const nlohmann::json &j, const std::string &name)
{ {
auto e = get_element(j, expand_name(j, name)); auto e = get_element(j, expand_name(j, name));