mirror of
https://github.com/bkryza/clang-uml.git
synced 2025-04-19 17:31:03 +08:00
Added option fold_repeated_activities to sequence diagrams (#317)
This commit is contained in:
parent
b12f7b2c27
commit
ddbc021cd9
@ -15,6 +15,7 @@ mermaid:
|
||||
generate_links:
|
||||
link: "{% if existsIn(element, \"doxygen_link\") %}{{ element.doxygen_link }}{% endif %}"
|
||||
tooltip: "{% if existsIn(element, \"comment\") and existsIn(element.comment, \"brief\") %}{{ abbrv(trim(replace(element.comment.brief.0, \"\\n+\", \" \")), 256) }}{% else %}{{ element.name }}{% endif %}"
|
||||
fold_repeated_activities: true
|
||||
diagrams:
|
||||
# Class diagrams
|
||||
class_translation_unit_visitor:
|
||||
|
@ -9,6 +9,7 @@
|
||||
* [Customizing participants order](#customizing-participants-order)
|
||||
* [Generating return types](#generating-return-types)
|
||||
* [Generating condition statements](#generating-condition-statements)
|
||||
* [Folding repeated activities](#folding-repeated-activities)
|
||||
* [Injecting call expressions manually through comments](#injecting-call-expressions-manually-through-comments)
|
||||
* [Including comments in sequence diagrams](#including-comments-in-sequence-diagrams)
|
||||
|
||||
@ -325,6 +326,23 @@ generate_condition_statements: true
|
||||
An example of a diagram with this feature enabled is presented below:
|
||||

|
||||
|
||||
## Folding repeated activities
|
||||
If in a given sequence diagram functions or methods are called multiple times
|
||||
from different branches, each of these activities will be rendered fully
|
||||
which can mean that the diagram be very large.
|
||||
|
||||
In order to minimize the diagram size in such situations, an option can be set:
|
||||
|
||||
```yaml
|
||||
fold_repeated_activities: true
|
||||
```
|
||||
|
||||
which will render any activity only once, and any further calls to that activity
|
||||
will only render a call to the activity and an indicator - a single `*` character
|
||||
- in a note over the activity.
|
||||
|
||||
For an example of this see the test case [t20056](test_cases/t20056.md).
|
||||
|
||||
## Injecting call expressions manually through comments
|
||||
In some cases, `clang-uml` is not yet able to discover a call expression target
|
||||
in some line of code. This can include passing function or method address to
|
||||
|
@ -569,6 +569,7 @@ cli_flow_t cli_handler::add_config_diagram(
|
||||
true;
|
||||
doc["diagrams"][name]["inline_lambda_messages"] = false;
|
||||
doc["diagrams"][name]["generate_message_comments"] = false;
|
||||
doc["diagrams"][name]["fold_repeated_activities"] = false;
|
||||
doc["diagrams"][name]["generate_condition_statements"] = false;
|
||||
doc["diagrams"][name]["using_namespace"] =
|
||||
std::vector<std::string>{{"myproject"}};
|
||||
|
@ -248,6 +248,7 @@ void inheritable_diagram_options::inherit(
|
||||
puml.override(parent.puml);
|
||||
mermaid.override(parent.mermaid);
|
||||
generate_method_arguments.override(parent.generate_method_arguments);
|
||||
fold_repeated_activities.override(parent.fold_repeated_activities);
|
||||
generate_concept_requirements.override(
|
||||
parent.generate_concept_requirements);
|
||||
generate_packages.override(parent.generate_packages);
|
||||
|
@ -606,6 +606,7 @@ struct inheritable_diagram_options {
|
||||
"generate_condition_statements", false};
|
||||
option<std::vector<std::string>> participants_order{"participants_order"};
|
||||
option<bool> generate_message_comments{"generate_message_comments", false};
|
||||
option<bool> fold_repeated_activities{"fold_repeated_activities", false};
|
||||
option<unsigned> message_comment_width{
|
||||
"message_comment_width", clanguml::util::kDefaultMessageCommentWidth};
|
||||
option<bool> debug_mode{"debug_mode", false};
|
||||
|
@ -249,6 +249,7 @@ types:
|
||||
generate_return_types: !optional bool
|
||||
generate_condition_statements: !optional bool
|
||||
generate_message_comments: !optional bool
|
||||
fold_repeated_activities: !optional bool
|
||||
message_comment_width: !optional int
|
||||
participants_order: !optional [string]
|
||||
start_from: !optional [source_location_t] # deprecated -> 'from'
|
||||
@ -377,6 +378,7 @@ root:
|
||||
generate_return_types: !optional bool
|
||||
generate_condition_statements: !optional bool
|
||||
generate_message_comments: !optional bool
|
||||
fold_repeated_activities: !optional bool
|
||||
message_comment_width: !optional int
|
||||
generate_packages: !optional bool
|
||||
group_methods: !optional bool
|
||||
|
@ -730,6 +730,7 @@ template <> struct convert<sequence_diagram> {
|
||||
get_option(node, rhs.participants_order);
|
||||
get_option(node, rhs.generate_method_arguments);
|
||||
get_option(node, rhs.generate_message_comments);
|
||||
get_option(node, rhs.fold_repeated_activities);
|
||||
get_option(node, rhs.message_comment_width);
|
||||
get_option(node, rhs.type_aliases);
|
||||
|
||||
@ -909,6 +910,7 @@ template <> struct convert<config> {
|
||||
get_option(node, rhs.generate_return_types);
|
||||
get_option(node, rhs.generate_condition_statements);
|
||||
get_option(node, rhs.generate_message_comments);
|
||||
get_option(node, rhs.fold_repeated_activities);
|
||||
get_option(node, rhs.message_comment_width);
|
||||
get_option(node, rhs.type_aliases);
|
||||
|
||||
|
@ -353,6 +353,7 @@ YAML::Emitter &operator<<(
|
||||
out << c.generate_return_types;
|
||||
out << c.participants_order;
|
||||
out << c.generate_message_comments;
|
||||
out << c.fold_repeated_activities;
|
||||
out << c.message_comment_width;
|
||||
}
|
||||
else if (const auto *pd = dynamic_cast<const package_diagram *>(&c);
|
||||
|
@ -22,7 +22,6 @@ namespace clanguml::sequence_diagram::generators::mermaid {
|
||||
|
||||
using clanguml::common::model::message_t;
|
||||
using clanguml::config::location_t;
|
||||
using clanguml::sequence_diagram::model::activity;
|
||||
using clanguml::sequence_diagram::model::message;
|
||||
using namespace clanguml::util;
|
||||
|
||||
@ -195,8 +194,25 @@ void generator::generate_return(const message &m, std::ostream &ostr) const
|
||||
}
|
||||
|
||||
void generator::generate_activity(
|
||||
const activity &a, std::ostream &ostr, std::vector<eid_t> &visited) const
|
||||
eid_t activity_id, std::ostream &ostr, std::vector<eid_t> &visited) const
|
||||
{
|
||||
const auto &a = model().get_activity(activity_id);
|
||||
|
||||
const auto [it, inserted] = generated_activities_.emplace(activity_id);
|
||||
|
||||
if (config().fold_repeated_activities() && !inserted &&
|
||||
!a.messages().empty()) {
|
||||
const auto &p =
|
||||
model().get_participant<model::participant>(activity_id);
|
||||
|
||||
if (p.has_value()) {
|
||||
ostr << indent(1) << "Note over " << generate_alias(p.value())
|
||||
<< " : *\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &m : a.messages()) {
|
||||
if (m.in_static_declaration_context()) {
|
||||
if (util::contains(already_generated_in_static_context_, m))
|
||||
@ -225,8 +241,7 @@ void generator::generate_activity(
|
||||
.end()) { // break infinite recursion on recursive calls
|
||||
LOG_DBG("Creating activity {} --> {} - missing sequence {}",
|
||||
m.from(), m.to(), m.to());
|
||||
generate_activity(
|
||||
model().get_activity(m.to()), ostr, visited);
|
||||
generate_activity(m.to(), ostr, visited);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -642,8 +657,7 @@ void generator::generate_diagram(std::ostream &ostr) const
|
||||
|
||||
ostr << indent(1) << "activate " << from_alias << '\n';
|
||||
|
||||
generate_activity(
|
||||
model().get_activity(start_from), ostr, visited_participants);
|
||||
generate_activity(start_from, ostr, visited_participants);
|
||||
|
||||
if (from.value().type_name() == "method" ||
|
||||
config().combine_free_functions_into_file_participants()) {
|
||||
|
@ -113,13 +113,13 @@ public:
|
||||
/**
|
||||
* @brief Generate sequence diagram activity.
|
||||
*
|
||||
* @param a Activity model
|
||||
* @param activity_id Activity id
|
||||
* @param ostr Output stream
|
||||
* @param visited List of already visited participants, this is necessary
|
||||
* for breaking infinite recursion on recursive calls
|
||||
*/
|
||||
void generate_activity(const clanguml::sequence_diagram::model::activity &a,
|
||||
std::ostream &ostr, std::vector<eid_t> &visited) const;
|
||||
void generate_activity(eid_t activity_id, std::ostream &ostr,
|
||||
std::vector<eid_t> &visited) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -158,6 +158,7 @@ private:
|
||||
mutable std::set<eid_t> generated_participants_;
|
||||
mutable std::set<unsigned int> generated_comment_ids_;
|
||||
mutable std::vector<model::message> already_generated_in_static_context_;
|
||||
mutable std::set<eid_t> generated_activities_;
|
||||
};
|
||||
|
||||
} // namespace mermaid
|
||||
|
@ -23,7 +23,6 @@ namespace clanguml::sequence_diagram::generators::plantuml {
|
||||
using clanguml::common::eid_t;
|
||||
using clanguml::common::model::message_t;
|
||||
using clanguml::config::location_t;
|
||||
using clanguml::sequence_diagram::model::activity;
|
||||
using clanguml::sequence_diagram::model::message;
|
||||
using namespace clanguml::util;
|
||||
|
||||
@ -152,8 +151,27 @@ void generator::generate_return(const message &m, std::ostream &ostr) const
|
||||
}
|
||||
|
||||
void generator::generate_activity(
|
||||
const activity &a, std::ostream &ostr, std::vector<eid_t> &visited) const
|
||||
eid_t activity_id, std::ostream &ostr, std::vector<eid_t> &visited) const
|
||||
{
|
||||
const auto &a = model().get_activity(activity_id);
|
||||
|
||||
const auto [it, inserted] = generated_activities_.emplace(activity_id);
|
||||
|
||||
if (config().fold_repeated_activities() && !inserted &&
|
||||
!a.messages().empty()) {
|
||||
const auto &p =
|
||||
model().get_participant<model::participant>(activity_id);
|
||||
|
||||
if (p.has_value()) {
|
||||
ostr << "hnote over " << generate_alias(p.value()) << " : *\n";
|
||||
// This is necessary to keep the hnote over the activity life line
|
||||
ostr << generate_alias(p.value()) << "-[hidden]->"
|
||||
<< generate_alias(p.value()) << '\n';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &m : a.messages()) {
|
||||
if (m.in_static_declaration_context()) {
|
||||
if (util::contains(already_generated_in_static_context_, m))
|
||||
@ -182,8 +200,7 @@ void generator::generate_activity(
|
||||
.end()) { // break infinite recursion on recursive calls
|
||||
LOG_DBG("Creating activity {} --> {} - missing sequence {}",
|
||||
m.from(), m.to(), m.to());
|
||||
generate_activity(
|
||||
model().get_activity(m.to()), ostr, visited);
|
||||
generate_activity(m.to(), ostr, visited);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -651,8 +668,7 @@ void generator::generate_diagram(std::ostream &ostr) const
|
||||
|
||||
ostr << "activate " << from_alias << '\n';
|
||||
|
||||
generate_activity(
|
||||
model().get_activity(start_from), ostr, visited_participants);
|
||||
generate_activity(start_from, ostr, visited_participants);
|
||||
|
||||
if (from.value().type_name() == "method" ||
|
||||
config().combine_free_functions_into_file_participants()) {
|
||||
|
@ -107,13 +107,13 @@ public:
|
||||
/**
|
||||
* @brief Generate sequence diagram activity.
|
||||
*
|
||||
* @param a Activity model
|
||||
* @param activity_id Activity id
|
||||
* @param ostr Output stream
|
||||
* @param visited List of already visited participants, this is necessary
|
||||
* for breaking infinite recursion on recursive calls
|
||||
*/
|
||||
void generate_activity(const clanguml::sequence_diagram::model::activity &a,
|
||||
std::ostream &ostr, std::vector<eid_t> &visited) const;
|
||||
void generate_activity(eid_t activity_id, std::ostream &ostr,
|
||||
std::vector<eid_t> &visited) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
@ -160,6 +160,7 @@ private:
|
||||
mutable std::set<eid_t> generated_participants_;
|
||||
mutable std::set<unsigned int> generated_comment_ids_;
|
||||
mutable std::vector<model::message> already_generated_in_static_context_;
|
||||
mutable std::set<eid_t> generated_activities_;
|
||||
};
|
||||
|
||||
} // namespace plantuml
|
||||
|
12
tests/t20056/.clang-uml
Normal file
12
tests/t20056/.clang-uml
Normal file
@ -0,0 +1,12 @@
|
||||
diagrams:
|
||||
t20056_sequence:
|
||||
type: sequence
|
||||
glob:
|
||||
- t20056.cc
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t20056
|
||||
using_namespace: clanguml::t20056
|
||||
fold_repeated_activities: true
|
||||
from:
|
||||
- function: "clanguml::t20056::tmain()"
|
46
tests/t20056/t20056.cc
Normal file
46
tests/t20056/t20056.cc
Normal file
@ -0,0 +1,46 @@
|
||||
namespace clanguml {
|
||||
namespace t20056 {
|
||||
struct A {
|
||||
void a() { aa(); }
|
||||
|
||||
void aa() { aaa(); }
|
||||
|
||||
void aaa() { }
|
||||
};
|
||||
|
||||
struct B {
|
||||
void b() { bb(); }
|
||||
|
||||
void bb() { bbb(); }
|
||||
|
||||
void bbb() { a.a(); }
|
||||
|
||||
A a;
|
||||
};
|
||||
|
||||
struct C {
|
||||
void c() { cc(); }
|
||||
|
||||
void cc() { ccc(); }
|
||||
|
||||
void ccc() { b.b(); }
|
||||
|
||||
B b;
|
||||
};
|
||||
|
||||
void tmain()
|
||||
{
|
||||
A a;
|
||||
B b;
|
||||
C c;
|
||||
|
||||
c.c();
|
||||
c.c();
|
||||
c.c();
|
||||
|
||||
b.b();
|
||||
|
||||
a.a();
|
||||
}
|
||||
}
|
||||
}
|
47
tests/t20056/test_case.h
Normal file
47
tests/t20056/test_case.h
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* tests/t20056/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("t20056")
|
||||
{
|
||||
using namespace clanguml::test;
|
||||
using namespace std::string_literals;
|
||||
|
||||
auto [config, db, diagram, model] =
|
||||
CHECK_SEQUENCE_MODEL("t20056", "t20056_sequence");
|
||||
|
||||
CHECK_SEQUENCE_DIAGRAM(*config, diagram, *model, [](const auto &src) {
|
||||
REQUIRE(MessageOrder(src,
|
||||
{
|
||||
// clang-format off
|
||||
{"tmain()", "C", "c()"},
|
||||
{"C", "C", "cc()"},
|
||||
{"C", "C", "ccc()"},
|
||||
{"C", "B", "b()"},
|
||||
{"B", "B", "bb()"},
|
||||
{"B", "B", "bbb()"},
|
||||
{"B", "A", "a()"},
|
||||
{"A", "A", "aa()"},
|
||||
{"A", "A", "aaa()"},
|
||||
{"tmain()", "C", "c()"},
|
||||
{"tmain()", "C", "c()"},
|
||||
{"tmain()", "B", "b()"},
|
||||
{"tmain()", "A", "a()"},
|
||||
// clang-format on
|
||||
}));
|
||||
});
|
||||
}
|
@ -634,6 +634,7 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
|
||||
#include "t20053/test_case.h"
|
||||
#include "t20054/test_case.h"
|
||||
#include "t20055/test_case.h"
|
||||
#include "t20056/test_case.h"
|
||||
|
||||
///
|
||||
/// Package diagram tests
|
||||
|
@ -412,6 +412,8 @@ test_cases:
|
||||
- name: t20055
|
||||
title: Test case for advanced filter in sequence diagram
|
||||
description:
|
||||
- name: t20056
|
||||
title: Test case for option to fold repeated activities in sequence diagram
|
||||
Package diagrams:
|
||||
- name: t30001
|
||||
title: Basic package diagram test case
|
||||
|
Loading…
x
Reference in New Issue
Block a user