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 {
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)
{
j["name"] = c.name();
@@ -94,6 +102,8 @@ void to_json(nlohmann::json &j, const class_ &c)
j["methods"] = c.methods();
j["bases"] = c.parents();
set_module(j, c);
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["is_nested"] = c.is_nested();
j["constants"] = c.constants();
set_module(j, 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["parameters"] = c.requires_parameters();
j["statements"] = c.requires_statements();
set_module(j, c);
}
} // namespace clanguml::class_diagram::model

View File

@@ -568,6 +568,31 @@ tvl::value_t access_filter::match(
[&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(
filter_t type, std::vector<config::context_config> context)
: filter_visitor{type}
@@ -924,6 +949,9 @@ void diagram_filter::init_filters(const config::diagram &c)
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));
@@ -1037,6 +1065,9 @@ void diagram_filter::init_filters(const config::diagram &c)
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));

View File

@@ -457,6 +457,20 @@ private:
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
* to any of the elements specified in context.

View File

@@ -101,6 +101,23 @@ public:
*/
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.
*
@@ -135,5 +152,6 @@ private:
namespace_ ns_;
namespace_ using_namespace_;
std::optional<std::string> module_;
bool module_private_{false};
};
} // 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)
{
switch (r) {

View File

@@ -23,6 +23,7 @@ namespace clanguml::common::model {
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 relationship_t {
@@ -77,6 +78,8 @@ std::string to_string(relationship_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(diagram_t r);

View File

@@ -168,7 +168,13 @@ void translation_unit_visitor::set_owning_module(
{
if (const clang::Module *module = decl.getOwningModule();
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

View File

@@ -194,6 +194,22 @@ struct filter {
*/
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
*
* Example:
@@ -245,8 +261,8 @@ struct filter {
*
* ```yaml
* include:
* relationships:
* - inheritance
* access:
* - public
* ```
*/
std::vector<common::model::access_t> access;

View File

@@ -91,6 +91,9 @@ types:
- public
- protected
- private
module_access_filter_t: !variant
- public
- private
method_type_filter_t: !variant
- constructor
- destructor
@@ -123,6 +126,7 @@ types:
element_types: !optional [element_types_filter_t]
relationships: !optional [relationship_filter_t]
access: !optional [access_filter_t]
module_access: !optional [module_access_filter_t]
subclasses: !optional [regex_or_string_t]
parents: !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::string_or_regex;
using clanguml::common::model::access_t;
using clanguml::common::model::module_access_t;
using clanguml::common::model::relationship_t;
using clanguml::config::callee_type;
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
//
@@ -483,6 +501,10 @@ template <> struct convert<filter> {
rhs.modules.push_back({ns});
}
if (node["module_access"])
rhs.module_access =
node["module_access"].as<decltype(rhs.module_access)>();
if (node["relationships"])
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;
}
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)
{
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;
if (!f.modules.empty())
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())
out << YAML::Key << "access" << YAML::Value << f.access;
if (!f.context.empty())

View File

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

View File

@@ -9,3 +9,8 @@ template <typename T> class BB {
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.lib2;

View File

@@ -35,7 +35,7 @@ TEST_CASE("t00070", "[test-case][class]")
REQUIRE_THAT(src, StartsWith("@startuml"));
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("C")));
@@ -43,6 +43,7 @@ TEST_CASE("t00070", "[test-case][class]")
REQUIRE_THAT(src, !IsClassTemplate("CC", "T"));
REQUIRE_THAT(src, IsEnum(_A("BBB")));
REQUIRE_THAT(src, !IsClass(_A("BBBB")));
REQUIRE_THAT(src, !IsEnum(_A("CCC")));
save_puml(config.output_directory(), diagram->name + ".puml", src);
@@ -53,6 +54,15 @@ TEST_CASE("t00070", "[test-case][class]")
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);
}
@@ -63,7 +73,7 @@ TEST_CASE("t00070", "[test-case][class]")
using mermaid::IsClass;
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("C")));
@@ -71,6 +81,7 @@ TEST_CASE("t00070", "[test-case][class]")
REQUIRE_THAT(src, !IsClass(_A("CC<T>")));
REQUIRE_THAT(src, IsEnum(_A("BBB")));
REQUIRE_THAT(src, !IsClass(_A("BBBB")));
REQUIRE_THAT(src, !IsEnum(_A("CCC")));
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";
}
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)
{
auto e = get_element(j, expand_name(j, name));