Added 'to' sequence diagram generator for plantuml and json
This commit is contained in:
@@ -543,6 +543,7 @@ struct sequence_diagram : public diagram {
|
|||||||
|
|
||||||
option<std::vector<source_location>> start_from{"start_from"};
|
option<std::vector<source_location>> start_from{"start_from"};
|
||||||
option<std::vector<std::vector<source_location>>> from_to{"from_to"};
|
option<std::vector<std::vector<source_location>>> from_to{"from_to"};
|
||||||
|
option<std::vector<source_location>> to{"to"};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ types:
|
|||||||
participants_order: !optional [string]
|
participants_order: !optional [string]
|
||||||
start_from: !optional [source_location_t]
|
start_from: !optional [source_location_t]
|
||||||
from_to: !optional [[source_location_t]]
|
from_to: !optional [[source_location_t]]
|
||||||
|
to: !optional [source_location_t]
|
||||||
package_diagram_t:
|
package_diagram_t:
|
||||||
type: !variant [package]
|
type: !variant [package]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -575,6 +575,7 @@ template <> struct convert<sequence_diagram> {
|
|||||||
|
|
||||||
get_option(node, rhs.start_from);
|
get_option(node, rhs.start_from);
|
||||||
get_option(node, rhs.from_to);
|
get_option(node, rhs.from_to);
|
||||||
|
get_option(node, rhs.to);
|
||||||
get_option(node, rhs.combine_free_functions_into_file_participants);
|
get_option(node, rhs.combine_free_functions_into_file_participants);
|
||||||
get_option(node, rhs.generate_return_types);
|
get_option(node, rhs.generate_return_types);
|
||||||
get_option(node, rhs.generate_condition_statements);
|
get_option(node, rhs.generate_condition_statements);
|
||||||
|
|||||||
@@ -349,6 +349,7 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const sequence_diagram &c)
|
|||||||
out << YAML::Key << "type" << YAML::Value << c.type();
|
out << YAML::Key << "type" << YAML::Value << c.type();
|
||||||
out << c.start_from;
|
out << c.start_from;
|
||||||
out << c.from_to;
|
out << c.from_to;
|
||||||
|
out << c.to;
|
||||||
out << dynamic_cast<const inheritable_diagram_options &>(c);
|
out << dynamic_cast<const inheritable_diagram_options &>(c);
|
||||||
out << YAML::EndMap;
|
out << YAML::EndMap;
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -617,8 +617,8 @@ void generator::generate_diagram(nlohmann::json &parent) const
|
|||||||
const auto &from_location = ft.front();
|
const auto &from_location = ft.front();
|
||||||
const auto &to_location = ft.back();
|
const auto &to_location = ft.back();
|
||||||
|
|
||||||
auto [from_activity_id, to_activity_id] =
|
auto from_activity_id = model().get_from_activity_id(from_location);
|
||||||
model().get_from_to_activity_ids(from_location, to_location);
|
auto to_activity_id = model().get_to_activity_id(to_location);
|
||||||
|
|
||||||
if (from_activity_id == 0 || to_activity_id == 0)
|
if (from_activity_id == 0 || to_activity_id == 0)
|
||||||
continue;
|
continue;
|
||||||
@@ -655,6 +655,42 @@ void generator::generate_diagram(nlohmann::json &parent) const
|
|||||||
json_["sequences"].push_back(std::move(sequence));
|
json_["sequences"].push_back(std::move(sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &to_location : config().to()) {
|
||||||
|
auto to_activity_id = model().get_to_activity_id(to_location);
|
||||||
|
|
||||||
|
if (to_activity_id == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto message_chains_unique =
|
||||||
|
model().get_all_from_to_message_chains(0, to_activity_id);
|
||||||
|
|
||||||
|
nlohmann::json sequence;
|
||||||
|
sequence["to"]["location"] = to_location.location;
|
||||||
|
sequence["to"]["id"] = to_activity_id;
|
||||||
|
|
||||||
|
block_statements_stack_.push_back(std::ref(sequence));
|
||||||
|
|
||||||
|
sequence["message_chains"] = nlohmann::json::array();
|
||||||
|
|
||||||
|
for (const auto &mc : message_chains_unique) {
|
||||||
|
nlohmann::json message_chain;
|
||||||
|
|
||||||
|
block_statements_stack_.push_back(std::ref(message_chain));
|
||||||
|
|
||||||
|
for (const auto &m : mc) {
|
||||||
|
generate_call(m, current_block_statement());
|
||||||
|
}
|
||||||
|
|
||||||
|
block_statements_stack_.pop_back();
|
||||||
|
|
||||||
|
sequence["message_chains"].push_back(std::move(message_chain));
|
||||||
|
}
|
||||||
|
|
||||||
|
block_statements_stack_.pop_back();
|
||||||
|
|
||||||
|
json_["sequences"].push_back(std::move(sequence));
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &sf : config().start_from()) {
|
for (const auto &sf : config().start_from()) {
|
||||||
if (sf.location_type == location_t::function) {
|
if (sf.location_type == location_t::function) {
|
||||||
common::model::diagram_element::id_t start_from{0};
|
common::model::diagram_element::id_t start_from{0};
|
||||||
|
|||||||
@@ -416,8 +416,8 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
const auto &from_location = ft.front();
|
const auto &from_location = ft.front();
|
||||||
const auto &to_location = ft.back();
|
const auto &to_location = ft.back();
|
||||||
|
|
||||||
auto [from_activity_id, to_activity_id] =
|
auto from_activity_id = model().get_from_activity_id(from_location);
|
||||||
model().get_from_to_activity_ids(from_location, to_location);
|
auto to_activity_id = model().get_to_activity_id(to_location);
|
||||||
|
|
||||||
if (from_activity_id == 0 || to_activity_id == 0)
|
if (from_activity_id == 0 || to_activity_id == 0)
|
||||||
continue;
|
continue;
|
||||||
@@ -451,6 +451,43 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &to_location : config().to()) {
|
||||||
|
auto to_activity_id = model().get_to_activity_id(to_location);
|
||||||
|
|
||||||
|
if (to_activity_id == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto message_chains_unique =
|
||||||
|
model().get_all_from_to_message_chains(0, to_activity_id);
|
||||||
|
|
||||||
|
bool first_separator_skipped{false};
|
||||||
|
for (const auto &mc : message_chains_unique) {
|
||||||
|
if (!first_separator_skipped)
|
||||||
|
first_separator_skipped = true;
|
||||||
|
else
|
||||||
|
ostr << "====\n";
|
||||||
|
|
||||||
|
const auto from_activity_id = mc.front().from();
|
||||||
|
|
||||||
|
const auto &from =
|
||||||
|
model().get_participant<model::function>(from_activity_id);
|
||||||
|
|
||||||
|
if (from.value().type_name() == "method" ||
|
||||||
|
config().combine_free_functions_into_file_participants()) {
|
||||||
|
generate_participant(ostr, from_activity_id);
|
||||||
|
ostr << "[->"
|
||||||
|
<< " " << generate_alias(from.value()) << " : "
|
||||||
|
<< from.value().message_name(
|
||||||
|
select_method_arguments_render_mode())
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &m : mc) {
|
||||||
|
generate_call(m, ostr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &sf : config().start_from()) {
|
for (const auto &sf : config().start_from()) {
|
||||||
if (sf.location_type == location_t::function) {
|
if (sf.location_type == location_t::function) {
|
||||||
common::model::diagram_element::id_t start_from{0};
|
common::model::diagram_element::id_t start_from{0};
|
||||||
|
|||||||
@@ -209,31 +209,11 @@ std::vector<std::string> diagram::list_start_from_values() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<common::model::diagram_element::id_t,
|
common::model::diagram_element::id_t diagram::get_to_activity_id(
|
||||||
common::model::diagram_element::id_t>
|
|
||||||
diagram::get_from_to_activity_ids(const config::source_location &from_location,
|
|
||||||
const config::source_location &to_location) const
|
const config::source_location &to_location) const
|
||||||
{
|
{
|
||||||
common::model::diagram_element::id_t from_activity{0};
|
|
||||||
common::model::diagram_element::id_t to_activity{0};
|
common::model::diagram_element::id_t to_activity{0};
|
||||||
|
|
||||||
for (const auto &[k, v] : sequences()) {
|
|
||||||
const auto &caller = *participants().at(v.from());
|
|
||||||
std::string vfrom = caller.full_name(false);
|
|
||||||
if (vfrom == from_location.location) {
|
|
||||||
LOG_DBG("Found sequence diagram start point '{}': {}", vfrom, k);
|
|
||||||
from_activity = k;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from_activity == 0) {
|
|
||||||
LOG_WARN("Failed to find 'from' participant {} for start_from "
|
|
||||||
"condition",
|
|
||||||
from_location.location);
|
|
||||||
return {from_activity, to_activity};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &[k, v] : sequences()) {
|
for (const auto &[k, v] : sequences()) {
|
||||||
for (const auto &m : v.messages()) {
|
for (const auto &m : v.messages()) {
|
||||||
if (m.type() != common::model::message_t::kCall)
|
if (m.type() != common::model::message_t::kCall)
|
||||||
@@ -250,13 +230,36 @@ diagram::get_from_to_activity_ids(const config::source_location &from_location,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (to_activity == 0) {
|
if (to_activity == 0) {
|
||||||
LOG_WARN("Failed to find 'to' participant {} for from_to "
|
LOG_WARN("Failed to find 'to' participant {} for to "
|
||||||
"condition",
|
"condition",
|
||||||
to_location.location);
|
to_location.location);
|
||||||
return {from_activity, to_activity};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {from_activity, to_activity};
|
return to_activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
common::model::diagram_element::id_t diagram::get_from_activity_id(
|
||||||
|
const config::source_location &from_location) const
|
||||||
|
{
|
||||||
|
common::model::diagram_element::id_t from_activity{0};
|
||||||
|
|
||||||
|
for (const auto &[k, v] : sequences()) {
|
||||||
|
const auto &caller = *participants().at(v.from());
|
||||||
|
std::string vfrom = caller.full_name(false);
|
||||||
|
if (vfrom == from_location.location) {
|
||||||
|
LOG_DBG("Found sequence diagram start point '{}': {}", vfrom, k);
|
||||||
|
from_activity = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from_activity == 0) {
|
||||||
|
LOG_WARN("Failed to find 'from' participant {} for from "
|
||||||
|
"condition",
|
||||||
|
from_location.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return from_activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_set<message_chain_t> diagram::get_all_from_to_message_chains(
|
std::unordered_set<message_chain_t> diagram::get_all_from_to_message_chains(
|
||||||
@@ -362,7 +365,8 @@ std::unordered_set<message_chain_t> diagram::get_all_from_to_message_chains(
|
|||||||
std::copy_if(message_chains.begin(), message_chains.end(),
|
std::copy_if(message_chains.begin(), message_chains.end(),
|
||||||
std::inserter(message_chains_unique, message_chains_unique.begin()),
|
std::inserter(message_chains_unique, message_chains_unique.begin()),
|
||||||
[from_activity](const message_chain_t &mc) {
|
[from_activity](const message_chain_t &mc) {
|
||||||
return !mc.empty() && (mc.front().from() == from_activity);
|
return !mc.empty() &&
|
||||||
|
(from_activity == 0 || (mc.front().from() == from_activity));
|
||||||
});
|
});
|
||||||
|
|
||||||
return message_chains_unique;
|
return message_chains_unique;
|
||||||
|
|||||||
@@ -241,6 +241,9 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Generate a list of message chains matching a from_to constraint
|
* @brief Generate a list of message chains matching a from_to constraint
|
||||||
*
|
*
|
||||||
|
* If 'from_activity' is 0, this method will return all message chains
|
||||||
|
* ending in 'to_activity'.
|
||||||
|
*
|
||||||
* @param from_activity Source activity for from_to message chain
|
* @param from_activity Source activity for from_to message chain
|
||||||
* @param to_activity Target activity for from_to message chain
|
* @param to_activity Target activity for from_to message chain
|
||||||
* @return List of message chains
|
* @return List of message chains
|
||||||
@@ -250,16 +253,22 @@ public:
|
|||||||
common::model::diagram_element::id_t to_activity) const;
|
common::model::diagram_element::id_t to_activity) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get ids of from and to activities in from_to constraint
|
* @brief Get id of a 'to' activity
|
||||||
*
|
*
|
||||||
* @param from_activity Source activity for from_to message chain
|
* @param to_location Target activity
|
||||||
* @param to_activity Target activity for from_to message chain
|
* @return Activity id
|
||||||
* @return Pair of activity ids (0 if not found)
|
|
||||||
*/
|
*/
|
||||||
std::pair<common::model::diagram_element::id_t,
|
common::model::diagram_element::id_t get_to_activity_id(
|
||||||
common::model::diagram_element::id_t>
|
const config::source_location &to_location) const;
|
||||||
get_from_to_activity_ids(const config::source_location &from_activity,
|
|
||||||
const config::source_location &to_activity) const;
|
/**
|
||||||
|
* @brief Get id of a 'from' activity
|
||||||
|
*
|
||||||
|
* @param from_location Source activity
|
||||||
|
* @return Activity id
|
||||||
|
*/
|
||||||
|
common::model::diagram_element::id_t get_from_activity_id(
|
||||||
|
const config::source_location &from_location) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Once the diagram is complete, run any final processing.
|
* @brief Once the diagram is complete, run any final processing.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace clanguml {
|
namespace clanguml {
|
||||||
namespace t20034 {
|
namespace t20034 {
|
||||||
struct A {
|
struct A {
|
||||||
|
|||||||
14
tests/t20036/.clang-uml
Normal file
14
tests/t20036/.clang-uml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
compilation_database_dir: ..
|
||||||
|
output_directory: puml
|
||||||
|
diagrams:
|
||||||
|
t20036_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- ../../tests/t20036/t20036.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20036
|
||||||
|
using_namespace:
|
||||||
|
- clanguml::t20036
|
||||||
|
to:
|
||||||
|
- function: "clanguml::t20036::A::a2()"
|
||||||
44
tests/t20036/t20036.cc
Normal file
44
tests/t20036/t20036.cc
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace clanguml {
|
||||||
|
namespace t20036 {
|
||||||
|
struct A {
|
||||||
|
void a1() { }
|
||||||
|
void a2() { }
|
||||||
|
void a3() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
void b1() { a.a2(); }
|
||||||
|
void b2() { a.a2(); }
|
||||||
|
void b3() { a.a3(); }
|
||||||
|
|
||||||
|
A a;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct C {
|
||||||
|
void c1() { b.b1(); }
|
||||||
|
void c2() { b.b2(); }
|
||||||
|
void c3()
|
||||||
|
{
|
||||||
|
if (reinterpret_cast<uint64_t>(&b) == 0xbadc0de)
|
||||||
|
c3();
|
||||||
|
else
|
||||||
|
c2();
|
||||||
|
}
|
||||||
|
|
||||||
|
void c4() { b.b2(); }
|
||||||
|
|
||||||
|
B b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct D {
|
||||||
|
void d1() { c.c2(); }
|
||||||
|
void d2() { c.c2(); }
|
||||||
|
void d3() { a.a2(); }
|
||||||
|
|
||||||
|
A a;
|
||||||
|
C c;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
73
tests/t20036/test_case.h
Normal file
73
tests/t20036/test_case.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20036/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2023 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("t20036", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20036");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20036_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20036_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20036_sequence");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto puml = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(puml);
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("C"), "c2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("D"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b1()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
save_puml(
|
||||||
|
config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto j = generate_sequence_json(diagram, *model);
|
||||||
|
|
||||||
|
using namespace json;
|
||||||
|
|
||||||
|
REQUIRE(HasMessageChain(j,
|
||||||
|
{{"C::c3()", "C::c2()", "void"}, {"C::c2()", "B::b2()", "void"},
|
||||||
|
{"B::b2()", "A::a2()", "void"}}));
|
||||||
|
REQUIRE(HasMessageChain(j,
|
||||||
|
{{"C::c4()", "B::b2()", "void"}, {"B::b2()", "A::a2()", "void"}}));
|
||||||
|
REQUIRE(HasMessageChain(j, {{"D::d3()", "A::a2()", "void"}}));
|
||||||
|
REQUIRE(HasMessageChain(j,
|
||||||
|
{{"D::d1()", "C::c2()", "void"}, {"C::c2()", "B::b2()", "void"},
|
||||||
|
{"B::b2()", "A::a2()", "void"}}));
|
||||||
|
REQUIRE(HasMessageChain(j,
|
||||||
|
{{"C::c1()", "B::b1()", "void"}, {"B::b1()", "A::a2()", "void"}}));
|
||||||
|
|
||||||
|
save_json(config.output_directory() + "/" + diagram->name + ".json", j);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -349,6 +349,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20033/test_case.h"
|
#include "t20033/test_case.h"
|
||||||
#include "t20034/test_case.h"
|
#include "t20034/test_case.h"
|
||||||
#include "t20035/test_case.h"
|
#include "t20035/test_case.h"
|
||||||
|
#include "t20036/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -299,11 +299,14 @@ test_cases:
|
|||||||
title: Control statement text in sequence diagram test case
|
title: Control statement text in sequence diagram test case
|
||||||
description:
|
description:
|
||||||
- name: t20034
|
- name: t20034
|
||||||
title: from_to sequence diagram test case
|
title: Test case for rendering all call chains from one activity to another (from_to)
|
||||||
description:
|
description:
|
||||||
- name: t20035
|
- name: t20035
|
||||||
title: from_to sequence diagram test case with free functions
|
title: from_to sequence diagram test case with free functions
|
||||||
description:
|
description:
|
||||||
|
- name: t20036
|
||||||
|
title: Test case for rendering all call chains leading to an activity (to)
|
||||||
|
description:
|
||||||
Package diagrams:
|
Package diagrams:
|
||||||
- name: t30001
|
- name: t30001
|
||||||
title: Basic package diagram test case
|
title: Basic package diagram test case
|
||||||
|
|||||||
Reference in New Issue
Block a user