Added default class method and member grouping and sorting

This commit is contained in:
Bartek Kryza
2023-05-31 22:46:39 +02:00
parent 097f7a11ed
commit c87bd7d94b
10 changed files with 285 additions and 119 deletions

View File

@@ -439,6 +439,7 @@ This project relies on the following great tools:
* [inja](https://github.com/pantor/inja) - a template engine for modern C++ * [inja](https://github.com/pantor/inja) - a template engine for modern C++
* [backward-cpp](https://github.com/bombela/backward-cpp) - stack trace pretty printer for C++ * [backward-cpp](https://github.com/bombela/backward-cpp) - stack trace pretty printer for C++
* [yaml-cpp](https://github.com/jbeder/yaml-cpp) - YAML parser library for C++ * [yaml-cpp](https://github.com/jbeder/yaml-cpp) - YAML parser library for C++
* [spdlog](https://github.com/gabime/spdlog) - Fast C++ logging library
## Contributing ## Contributing

View File

@@ -123,10 +123,6 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
{ {
namespace plantuml_common = clanguml::common::generators::plantuml; namespace plantuml_common = clanguml::common::generators::plantuml;
constexpr auto kAbbreviatedMethodArgumentsLength{15};
const auto &uns = m_config.using_namespace();
std::string class_type{"class"}; std::string class_type{"class"};
if (c.is_abstract()) if (c.is_abstract())
class_type = "abstract"; class_type = "abstract";
@@ -149,10 +145,143 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
// //
// Process methods // Process methods
// //
for (const auto &m : c.methods()) { if (m_config.group_methods()) {
generate_methods(group_methods(c.methods()), ostr);
}
else {
generate_methods(c.methods(), ostr);
}
//
// Process relationships - here only generate the set of
// rendered_relationships we'll generate them in a seperate method
//
std::set<std::string> rendered_relations;
std::stringstream all_relations_str;
for (const auto &r : c.relationships()) {
if (!m_model.should_include(r.type()))
continue;
try {
generate_relationship(r, rendered_relations, ostr);
}
catch (error::uml_alias_missing &e) {
LOG_DBG("Skipping {} relation from {} to {} due "
"to: {}",
plantuml_common::to_plantuml(r.type(), r.style()),
c.full_name(), r.destination(), e.what());
}
}
//
// Process members
//
std::vector<clanguml::class_diagram::model::class_member> members{
c.members()};
sort_class_elements(members);
if (m_config.group_methods())
ostr << "__\n";
for (const auto &m : members) {
if (!m_model.should_include(m.access())) if (!m_model.should_include(m.access()))
continue; continue;
if (!m_config.include_relations_also_as_members() &&
rendered_relations.find(m.name()) != rendered_relations.end())
continue;
generate_member(m, ostr);
ostr << '\n';
}
ostr << "}" << '\n';
generate_notes(ostr, c);
for (const auto &member : c.members())
generate_member_notes(ostr, member, c.alias());
for (const auto &method : c.methods())
generate_member_notes(ostr, method, c.alias());
}
void generator::generate_methods(
const method_groups_t &methods, std::ostream &ostr) const
{
bool is_first_non_empty_group{true};
for (const auto &group : method_groups_) {
const auto &group_methods = methods.at(group);
if (!group_methods.empty()) {
if (!is_first_non_empty_group)
ostr << "..\n";
is_first_non_empty_group = false;
generate_methods(group_methods, ostr);
}
}
}
void generator::generate_methods(
const std::vector<class_method> &methods, std::ostream &ostr) const
{
auto sorted_methods = methods;
sort_class_elements(sorted_methods);
for (const auto &m : sorted_methods) {
if (!m_model.should_include(m.access()))
continue;
generate_method(m, ostr);
ostr << '\n';
}
}
generator::method_groups_t generator::group_methods(
const std::vector<class_method> &methods) const
{
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 m_model.should_include(m.access()); });
for (const auto &g : method_groups_) {
result[g] = {};
}
for (const auto &m : filtered_methods) {
if (m.is_constructor() || m.is_destructor()) {
result["constructors"].push_back(m);
}
else if (m.is_copy_assignment() || m.is_move_assignment()) {
result["assignment"].push_back(m);
}
else if (m.is_operator()) {
result["operators"].push_back(m);
}
else {
result["other"].push_back(m);
}
}
return result;
}
void generator::generate_method(
const class_diagram::model::class_method &m, std::ostream &ostr) const
{
namespace plantuml_common = clanguml::common::generators::plantuml;
const auto &uns = m_config.using_namespace();
constexpr auto kAbbreviatedMethodArgumentsLength{15};
print_debug(m, ostr); print_debug(m, ostr);
if (m.is_pure_virtual()) if (m.is_pure_virtual())
@@ -161,8 +290,7 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
if (m.is_static()) if (m.is_static())
ostr << "{static} "; ostr << "{static} ";
std::string type{ std::string type{uns.relative(m_config.simplify_template_type(m.type()))};
uns.relative(m_config.simplify_template_type(m.type()))};
ostr << plantuml_common::to_plantuml(m.access()) << m.name(); ostr << plantuml_common::to_plantuml(m.access()) << m.name();
@@ -215,69 +343,13 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
if (m_config.generate_links) { if (m_config.generate_links) {
generate_link(ostr, m); generate_link(ostr, m);
} }
}
ostr << '\n'; void generator::generate_member(
} const class_diagram::model::class_member &m, std::ostream &ostr) const
{
// namespace plantuml_common = clanguml::common::generators::plantuml;
// Process relationships - here only generate the set of const auto &uns = m_config.using_namespace();
// rendered_relationships we'll generate them in a seperate method
//
std::set<std::string> rendered_relations;
std::stringstream all_relations_str;
std::set<std::string> unique_relations;
for (const auto &r : c.relationships()) {
if (!m_model.should_include(r.type()))
continue;
LOG_DBG("Processing relationship {}",
plantuml_common::to_plantuml(r.type(), r.style()));
std::string destination;
try {
auto target_element = m_model.get(r.destination());
if (!target_element.has_value())
throw error::uml_alias_missing{
fmt::format("Missing element in the model for ID: {}",
r.destination())};
destination = target_element.value().full_name(false);
if (util::starts_with(destination, std::string{"::"}))
destination = destination.substr(2, destination.size());
std::string puml_relation;
if (!r.multiplicity_source().empty())
puml_relation += "\"" + r.multiplicity_source() + "\" ";
puml_relation += plantuml_common::to_plantuml(r.type(), r.style());
if (!r.multiplicity_destination().empty())
puml_relation += " \"" + r.multiplicity_destination() + "\"";
if (!r.label().empty()) {
rendered_relations.emplace(r.label());
}
}
catch (error::uml_alias_missing &e) {
LOG_DBG("Skipping {} relation from {} to {} due "
"to: {}",
plantuml_common::to_plantuml(r.type(), r.style()),
c.full_name(), destination, e.what());
}
}
//
// Process members
//
for (const auto &m : c.members()) {
if (!m_model.should_include(m.access()))
continue;
if (!m_config.include_relations_also_as_members() &&
rendered_relations.find(m.name()) != rendered_relations.end())
continue;
print_debug(m, ostr); print_debug(m, ostr);
@@ -291,19 +363,6 @@ void generator::generate(const class_ &c, std::ostream &ostr) const
if (m_config.generate_links) { if (m_config.generate_links) {
generate_link(ostr, m); generate_link(ostr, m);
} }
ostr << '\n';
}
ostr << "}" << '\n';
generate_notes(ostr, c);
for (const auto &member : c.members())
generate_member_notes(ostr, member, c.alias());
for (const auto &method : c.methods())
generate_member_notes(ostr, method, c.alias());
} }
void generator::generate(const concept_ &c, std::ostream &ostr) const void generator::generate(const concept_ &c, std::ostream &ostr) const
@@ -377,6 +436,40 @@ void generator::generate_relationships(std::ostream &ostr) const
} }
} }
void generator::generate_relationship(const relationship &r,
std::set<std::string> &rendered_relations, std::ostream &ostr) const
{
namespace plantuml_common = clanguml::common::generators::plantuml;
LOG_DBG("Processing relationship {}",
plantuml_common::to_plantuml(r.type(), r.style()));
std::string destination;
auto target_element = m_model.get(r.destination());
if (!target_element.has_value())
throw error::uml_alias_missing{fmt::format(
"Missing element in the model for ID: {}", r.destination())};
destination = target_element.value().full_name(false);
if (util::starts_with(destination, std::string{"::"}))
destination = destination.substr(2, destination.size());
std::string puml_relation;
if (!r.multiplicity_source().empty())
puml_relation += "\"" + r.multiplicity_source() + "\" ";
puml_relation += plantuml_common::to_plantuml(r.type(), r.style());
if (!r.multiplicity_destination().empty())
puml_relation += " \"" + r.multiplicity_destination() + "\"";
if (!r.label().empty()) {
rendered_relations.emplace(r.label());
}
}
void generator::generate_relationships( void generator::generate_relationships(
const class_ &c, std::ostream &ostr) const const class_ &c, std::ostream &ostr) const
{ {
@@ -732,8 +825,8 @@ void generator::generate_relationships(
for (const auto &subpackage : p) { for (const auto &subpackage : p) {
if (dynamic_cast<package *>(subpackage.get()) != nullptr) { if (dynamic_cast<package *>(subpackage.get()) != nullptr) {
// TODO: add option - generate_empty_packages, currently // TODO: add option - generate_empty_packages, currently
// packages which do not contain anything but other packages // packages which do not contain anything but other
// are skipped // packages are skipped
const auto &sp = dynamic_cast<package &>(*subpackage); const auto &sp = dynamic_cast<package &>(*subpackage);
if (!sp.is_empty() && if (!sp.is_empty() &&
!sp.all_of([this](const common::model::element &e) { !sp.all_of([this](const common::model::element &e) {

View File

@@ -48,20 +48,24 @@ using common_generator =
using clanguml::class_diagram::model::class_; using clanguml::class_diagram::model::class_;
using clanguml::class_diagram::model::class_element; using clanguml::class_diagram::model::class_element;
using clanguml::class_diagram::model::class_member;
using clanguml::class_diagram::model::class_method;
using clanguml::class_diagram::model::concept_; using clanguml::class_diagram::model::concept_;
using clanguml::class_diagram::model::enum_; using clanguml::class_diagram::model::enum_;
using clanguml::common::model::access_t; using clanguml::common::model::access_t;
using clanguml::common::model::package; using clanguml::common::model::package;
using clanguml::common::model::relationship;
using clanguml::common::model::relationship_t; using clanguml::common::model::relationship_t;
using namespace clanguml::util; using namespace clanguml::util;
class generator : public common_generator<diagram_config, diagram_model> { class generator : public common_generator<diagram_config, diagram_model> {
using method_groups_t = std::map<std::string, std::vector<class_method>>;
public: public:
generator(diagram_config &config, diagram_model &model); generator(diagram_config &config, diagram_model &model);
void generate_link( void generate_link(std::ostream &ostr, const class_element &e) const;
std::ostream &ostr, const class_diagram::model::class_element &e) const;
void generate_alias(const class_ &c, std::ostream &ostr) const; void generate_alias(const class_ &c, std::ostream &ostr) const;
@@ -71,12 +75,25 @@ public:
void generate(const class_ &c, std::ostream &ostr) const; void generate(const class_ &c, std::ostream &ostr) const;
void generate_methods(
const std::vector<class_method> &methods, std::ostream &ostr) const;
void generate_methods(
const method_groups_t &methods, std::ostream &ostr) const;
void generate_method(const class_method &m, std::ostream &ostr) const;
void generate_member(const class_member &m, std::ostream &ostr) const;
void generate_top_level_elements(std::ostream &ostr) const; void generate_top_level_elements(std::ostream &ostr) const;
void generate_relationships(std::ostream &ostr) const; void generate_relationships(std::ostream &ostr) const;
void generate_relationships(const class_ &c, std::ostream &ostr) const; void generate_relationships(const class_ &c, std::ostream &ostr) const;
void generate_relationship(const relationship &r,
std::set<std::string> &rendered_relations, std::ostream &ostr) const;
void generate(const enum_ &e, std::ostream &ostr) const; void generate(const enum_ &e, std::ostream &ostr) const;
void generate_relationships(const enum_ &c, std::ostream &ostr) const; void generate_relationships(const enum_ &c, std::ostream &ostr) const;
@@ -96,9 +113,26 @@ public:
void generate(std::ostream &ostr) const override; void generate(std::ostream &ostr) const override;
method_groups_t group_methods(
const std::vector<class_method> &methods) const;
private: private:
const std::vector<std::string> method_groups_{
"constructors", "assignment", "operators", "other"};
std::string render_name(std::string name) const; std::string render_name(std::string name) const;
template <typename T>
void sort_class_elements(std::vector<T> &elements) const
{
if (m_config.member_order() == config::member_order_t::lexical) {
std::sort(elements.begin(), elements.end(),
[](const auto &m1, const auto &m2) {
return m1.name() < m2.name();
});
}
}
mutable common::generators::nested_element_stack<common::model::element> mutable common::generators::nested_element_stack<common::model::element>
together_group_stack_; together_group_stack_;
}; };

View File

@@ -81,6 +81,13 @@ void class_method::is_constructor(bool is_constructor)
is_constructor_ = is_constructor; is_constructor_ = is_constructor;
} }
bool class_method::is_destructor() const { return is_destructor_; }
void class_method::is_destructor(bool is_destructor)
{
is_destructor_ = is_destructor;
}
bool class_method::is_move_assignment() const { return is_move_assignment_; } bool class_method::is_move_assignment() const { return is_move_assignment_; }
void class_method::is_move_assignment(bool is_move_assignment) void class_method::is_move_assignment(bool is_move_assignment)

View File

@@ -66,6 +66,9 @@ public:
bool is_constructor() const; bool is_constructor() const;
void is_constructor(bool is_constructor); void is_constructor(bool is_constructor);
bool is_destructor() const;
void is_destructor(bool is_destructor);
bool is_move_assignment() const; bool is_move_assignment() const;
void is_move_assignment(bool is_move_assignment); void is_move_assignment(bool is_move_assignment);
@@ -91,6 +94,7 @@ private:
bool is_constexpr_{false}; bool is_constexpr_{false};
bool is_consteval_{false}; bool is_consteval_{false};
bool is_constructor_{false}; bool is_constructor_{false};
bool is_destructor_{false};
bool is_move_assignment_{false}; bool is_move_assignment_{false};
bool is_copy_assignment_{false}; bool is_copy_assignment_{false};
bool is_operator_{false}; bool is_operator_{false};

View File

@@ -1284,6 +1284,7 @@ void translation_unit_visitor::process_method(
util::trim(method_name), method_return_type}; util::trim(method_name), method_return_type};
const bool is_constructor = c.name() == method_name; const bool is_constructor = c.name() == method_name;
const bool is_destructor = fmt::format("~{}", c.name()) == method_name;
method.is_pure_virtual(mf.isPure()); method.is_pure_virtual(mf.isPure());
method.is_virtual(mf.isVirtual()); method.is_virtual(mf.isVirtual());
@@ -1295,6 +1296,7 @@ void translation_unit_visitor::process_method(
method.is_constexpr(mf.isConstexprSpecified() && !is_constructor); method.is_constexpr(mf.isConstexprSpecified() && !is_constructor);
method.is_consteval(mf.isConsteval()); method.is_consteval(mf.isConsteval());
method.is_constructor(is_constructor); method.is_constructor(is_constructor);
method.is_destructor(is_destructor);
method.is_move_assignment(mf.isMoveAssignmentOperator()); method.is_move_assignment(mf.isMoveAssignmentOperator());
method.is_copy_assignment(mf.isCopyAssignmentOperator()); method.is_copy_assignment(mf.isCopyAssignmentOperator());
method.is_noexcept(isNoexceptExceptionSpec(mf.getExceptionSpecType())); method.is_noexcept(isNoexceptExceptionSpec(mf.getExceptionSpecType()));

View File

@@ -39,6 +39,8 @@ enum class method_arguments { full, abbreviated, none };
enum class package_type_t { kNamespace, kDirectory }; enum class package_type_t { kNamespace, kDirectory };
enum class member_order_t { lexical, as_is };
std::string to_string(method_arguments ma); std::string to_string(method_arguments ma);
enum class comment_parser_t { plain, clang }; enum class comment_parser_t { plain, clang };
@@ -163,6 +165,9 @@ struct inheritable_diagram_options {
option<plantuml> puml{"plantuml", option_inherit_mode::kAppend}; option<plantuml> puml{"plantuml", option_inherit_mode::kAppend};
option<method_arguments> generate_method_arguments{ option<method_arguments> generate_method_arguments{
"generate_method_arguments", method_arguments::full}; "generate_method_arguments", method_arguments::full};
option<bool> group_methods{"group_methods", true};
option<member_order_t> member_order{
"method_order", member_order_t::lexical};
option<bool> generate_packages{"generate_packages", false}; option<bool> generate_packages{"generate_packages", false};
option<package_type_t> package_type{ option<package_type_t> package_type{
"package_type", package_type_t::kNamespace}; "package_type", package_type_t::kNamespace};

View File

@@ -32,6 +32,7 @@ using clanguml::config::hint_t;
using clanguml::config::include_diagram; using clanguml::config::include_diagram;
using clanguml::config::layout_hint; using clanguml::config::layout_hint;
using clanguml::config::location_t; using clanguml::config::location_t;
using clanguml::config::member_order_t;
using clanguml::config::method_arguments; using clanguml::config::method_arguments;
using clanguml::config::package_diagram; using clanguml::config::package_diagram;
using clanguml::config::package_type_t; using clanguml::config::package_type_t;
@@ -89,6 +90,21 @@ void get_option<method_arguments>(
} }
} }
template <>
void get_option<member_order_t>(
const Node &node, clanguml::config::option<member_order_t> &option)
{
if (node[option.name]) {
const auto &val = node[option.name].as<std::string>();
if (val == "as_is")
option.set(member_order_t::as_is);
else if (val == "lexical")
option.set(member_order_t::lexical);
else
throw std::runtime_error("Invalid member_order value: " + val);
}
}
template <> template <>
void get_option<package_type_t>( void get_option<package_type_t>(
const Node &node, clanguml::config::option<package_type_t> &option) const Node &node, clanguml::config::option<package_type_t> &option)
@@ -139,7 +155,6 @@ void get_option<std::map<std::string, clanguml::config::diagram_template>>(
YAML::Node included_node = YAML::LoadFile(include_path.string()); YAML::Node included_node = YAML::LoadFile(include_path.string());
// diagram_config = parse_diagram_config(included_node);
option.set( option.set(
included_node.as< included_node.as<
std::map<std::string, clanguml::config::diagram_template>>()); std::map<std::string, clanguml::config::diagram_template>>());
@@ -419,6 +434,8 @@ template <> struct convert<class_diagram> {
get_option(node, rhs.layout); get_option(node, rhs.layout);
get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.include_relations_also_as_members);
get_option(node, rhs.generate_method_arguments); get_option(node, rhs.generate_method_arguments);
get_option(node, rhs.group_methods);
get_option(node, rhs.member_order);
get_option(node, rhs.generate_packages); get_option(node, rhs.generate_packages);
get_option(node, rhs.package_type); get_option(node, rhs.package_type);
get_option(node, rhs.relationship_hints); get_option(node, rhs.relationship_hints);

View File

@@ -304,6 +304,7 @@ using namespace clanguml::test::matchers;
#if defined(ENABLE_CXX_STD_20_TEST_CASES) #if defined(ENABLE_CXX_STD_20_TEST_CASES)
#include "t00065/test_case.h" #include "t00065/test_case.h"
#endif #endif
#include "t00066/test_case.h"
/// ///
/// Sequence diagram tests /// Sequence diagram tests

View File

@@ -192,6 +192,8 @@ test_cases:
- name: t00065 - name: t00065
title: Class diagram with packages from directory structure title: Class diagram with packages from directory structure
description: description:
- name: t00066
title: Class fields and methods without grouping and sorting
Sequence diagrams: Sequence diagrams:
- name: t20001 - name: t20001
title: Basic sequence diagram test case title: Basic sequence diagram test case