Skip to content

Commit

Permalink
Added option fold_repeated_activities to sequence diagrams (#317)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkryza committed Sep 11, 2024
1 parent b12f7b2 commit ddbc021
Show file tree
Hide file tree
Showing 17 changed files with 185 additions and 18 deletions.
1 change: 1 addition & 0 deletions .clang-uml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
18 changes: 18 additions & 0 deletions docs/sequence_diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -325,6 +326,23 @@ generate_condition_statements: true
An example of a diagram with this feature enabled is presented below:
![extension](test_cases/t20033_sequence.svg)

## 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
Expand Down
1 change: 1 addition & 0 deletions src/cli/cli_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"}};
Expand Down
1 change: 1 addition & 0 deletions src/config/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 2 additions & 0 deletions src/config/schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ const std::string schema_str = R"(
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'
Expand Down Expand Up @@ -377,6 +378,7 @@ const std::string schema_str = R"(
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
Expand Down
2 changes: 2 additions & 0 deletions src/config/yaml_decoders.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/config/yaml_emitters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ class generator : public common_generator<diagram_config, diagram_model> {
/**
* @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:
/**
Expand Down Expand Up @@ -158,6 +158,7 @@ class generator : public common_generator<diagram_config, diagram_model> {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ class generator : public common_generator<diagram_config, diagram_model> {
/**
* @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:
/**
Expand Down Expand Up @@ -160,6 +160,7 @@ class generator : public common_generator<diagram_config, diagram_model> {
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
Expand Down
12 changes: 12 additions & 0 deletions tests/t20056/.clang-uml
Original file line number Diff line number Diff line change
@@ -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 changes: 46 additions & 0 deletions tests/t20056/t20056.cc
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions tests/t20056/test_case.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* tests/t20056/test_case.h
*
* Copyright (c) 2021-2024 Bartek Kryza <[email protected]>
*
* 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
}));
});
}
1 change: 1 addition & 0 deletions tests/test_cases.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/test_cases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ddbc021

Please sign in to comment.