From f0497e934dfd8292c1cd1772d389131196a7fd2d Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 19 Mar 2023 18:29:45 +0100 Subject: [PATCH] First working version of JSON sequence diagram generator --- README.md | 2 +- .../json/class_diagram_generator.cc | 56 +- .../generators/json/class_diagram_generator.h | 4 +- src/common/generators/generators.h | 1 + src/common/generators/json/generator.cc | 72 +++ src/common/generators/json/generator.h | 12 + src/common/model/enums.cc | 13 + src/common/model/enums.h | 2 + .../json/sequence_diagram_generator.cc | 546 ++++++++++++++++++ .../json/sequence_diagram_generator.h | 81 +++ .../plantuml/sequence_diagram_generator.cc | 4 + tests/t20001/test_case.h | 16 + tests/t20002/test_case.h | 6 + tests/t20003/test_case.h | 6 + tests/t20004/test_case.h | 6 + tests/t20005/test_case.h | 6 + tests/t20006/test_case.h | 6 + tests/t20007/test_case.h | 6 + tests/t20008/test_case.h | 6 + tests/t20009/test_case.h | 6 + tests/t20010/test_case.h | 6 + tests/t20011/test_case.h | 6 + tests/t20012/test_case.h | 6 + tests/t20013/test_case.h | 6 + tests/t20014/test_case.h | 6 + tests/t20015/test_case.h | 6 + tests/t20016/test_case.h | 6 + tests/t20017/test_case.h | 6 + tests/t20018/test_case.h | 6 + tests/t20019/test_case.h | 6 + tests/t20020/test_case.h | 6 + tests/t20021/test_case.h | 6 + tests/t20022/test_case.h | 6 + tests/t20023/test_case.h | 6 + tests/t20024/test_case.h | 6 + tests/t20025/test_case.h | 6 + tests/t20026/test_case.h | 6 + tests/t20027/test_case.h | 6 + tests/t20028/test_case.h | 6 + tests/t20029/test_case.h | 227 ++++++++ tests/test_cases.cc | 14 + util/generate_test_cases_docs.py | 9 + 42 files changed, 1165 insertions(+), 56 deletions(-) create mode 100644 src/common/generators/json/generator.cc create mode 100644 src/sequence_diagram/generators/json/sequence_diagram_generator.cc create mode 100644 src/sequence_diagram/generators/json/sequence_diagram_generator.h diff --git a/README.md b/README.md index d7c58db6..a0126b87 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ YAML configuration files. The main idea behind the project is to easily maintain up-to-date diagrams within a code-base or document legacy code. The configuration file or files for `clang-uml` define the type and contents of each generated diagram. -Currently, the diagrams are generated in [PlantUML](https://plantuml.com) format. +Currently, the diagrams are generated in [PlantUML](https://plantuml.com) and JSON formats. `clang-uml` currently supports C++ up to version 17 and partial support for C++ 20. diff --git a/src/class_diagram/generators/json/class_diagram_generator.cc b/src/class_diagram/generators/json/class_diagram_generator.cc index a4b889fe..50be3967 100644 --- a/src/class_diagram/generators/json/class_diagram_generator.cc +++ b/src/class_diagram/generators/json/class_diagram_generator.cc @@ -20,59 +20,6 @@ #include "util/error.h" -namespace clanguml::common::model { -using nlohmann::json; - -void to_json(nlohmann::json &j, const source_location &sl) -{ - j = json{{"file", sl.file_relative()}, {"line", sl.line()}}; -} - -void to_json(nlohmann::json &j, const element &c) -{ - j = json{{"id", std::to_string(c.id())}, {"name", c.name()}, - {"namespace", c.get_namespace().to_string()}, {"type", c.type_name()}, - {"display_name", c.full_name(false)}}; - - if (const auto &comment = c.comment(); comment) - j["comment"] = comment.value(); - - if (!c.file().empty()) { - j["source_location"] = - dynamic_cast(c); - } -} - -void to_json(nlohmann::json &j, const template_parameter &c) -{ - j["kind"] = to_string(c.kind()); - if (c.type()) - j["type"] = c.type().value(); - if (c.name()) - j["name"] = c.name().value(); - if (c.default_value()) - j["default"] = c.default_value().value(); - j["is_variadic"] = c.is_variadic(); -} - -void to_json(nlohmann::json &j, const relationship &c) -{ - j["type"] = to_string(c.type()); - j["destination"] = std::to_string(c.destination()); - if (!c.multiplicity_source().empty()) - j["multiplicity_source"] = c.multiplicity_source(); - if (!c.multiplicity_destination().empty()) - j["multiplicity_destination"] = c.multiplicity_destination(); - if (c.access() != access_t::kNone) - j["access"] = to_string(c.access()); - if (!c.label().empty()) - j["label"] = c.label(); - if (const auto &comment = c.comment(); comment) - j["comment"] = comment.value(); -} - -} // namespace clanguml::common::model - namespace clanguml::class_diagram::model { using nlohmann::json; void to_json(nlohmann::json &j, const class_element &c) @@ -170,6 +117,9 @@ void generator::generate(std::ostream &ostr) const generate_relationships(json_); + json_["name"] = m_model.name(); + json_["diagram_type"] = "class"; + ostr << json_; } diff --git a/src/class_diagram/generators/json/class_diagram_generator.h b/src/class_diagram/generators/json/class_diagram_generator.h index 30f51f5b..ef29a680 100644 --- a/src/class_diagram/generators/json/class_diagram_generator.h +++ b/src/class_diagram/generators/json/class_diagram_generator.h @@ -60,6 +60,8 @@ class generator : public common_generator { public: generator(diagram_config &config, diagram_model &model); + void generate(std::ostream &ostr) const override; + void generate(const class_ &c, nlohmann::json &parent) const; void generate(const enum_ &c, nlohmann::json &parent) const; @@ -70,8 +72,6 @@ public: void generate_top_level_elements(nlohmann::json &parent) const; - void generate(std::ostream &ostr) const override; - void generate_relationships(nlohmann::json &parent) const; void generate_relationships(const class_ &c, nlohmann::json &parent) const; diff --git a/src/common/generators/generators.h b/src/common/generators/generators.h index 8cf7874a..67d924ad 100644 --- a/src/common/generators/generators.h +++ b/src/common/generators/generators.h @@ -25,6 +25,7 @@ #include "config/config.h" #include "include_diagram/generators/plantuml/include_diagram_generator.h" #include "package_diagram/generators/plantuml/package_diagram_generator.h" +#include "sequence_diagram/generators/json/sequence_diagram_generator.h" #include "sequence_diagram/generators/plantuml/sequence_diagram_generator.h" #include "util/util.h" #include "version.h" diff --git a/src/common/generators/json/generator.cc b/src/common/generators/json/generator.cc new file mode 100644 index 00000000..b02f8710 --- /dev/null +++ b/src/common/generators/json/generator.cc @@ -0,0 +1,72 @@ +/** + * src/common/generators/json/generator.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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. + */ + +#include "generator.h" + +namespace clanguml::common::model { +using nlohmann::json; + +void to_json(nlohmann::json &j, const source_location &sl) +{ + j = json{{"file", sl.file_relative()}, {"line", sl.line()}}; +} + +void to_json(nlohmann::json &j, const element &c) +{ + j = json{{"id", std::to_string(c.id())}, {"name", c.name()}, + {"namespace", c.get_namespace().to_string()}, {"type", c.type_name()}, + {"display_name", c.full_name(false)}}; + + if (const auto &comment = c.comment(); comment) + j["comment"] = comment.value(); + + if (!c.file().empty()) { + j["source_location"] = + dynamic_cast(c); + } +} + +void to_json(nlohmann::json &j, const template_parameter &c) +{ + j["kind"] = to_string(c.kind()); + if (c.type()) + j["type"] = c.type().value(); + if (c.name()) + j["name"] = c.name().value(); + if (c.default_value()) + j["default"] = c.default_value().value(); + j["is_variadic"] = c.is_variadic(); +} + +void to_json(nlohmann::json &j, const relationship &c) +{ + j["type"] = to_string(c.type()); + j["destination"] = std::to_string(c.destination()); + if (!c.multiplicity_source().empty()) + j["multiplicity_source"] = c.multiplicity_source(); + if (!c.multiplicity_destination().empty()) + j["multiplicity_destination"] = c.multiplicity_destination(); + if (c.access() != access_t::kNone) + j["access"] = to_string(c.access()); + if (!c.label().empty()) + j["label"] = c.label(); + if (const auto &comment = c.comment(); comment) + j["comment"] = comment.value(); +} + +} // namespace clanguml::common::model diff --git a/src/common/generators/json/generator.h b/src/common/generators/json/generator.h index 0a542edb..0d2f143d 100644 --- a/src/common/generators/json/generator.h +++ b/src/common/generators/json/generator.h @@ -29,6 +29,18 @@ #include +namespace clanguml::common::model { +using nlohmann::json; + +void to_json(nlohmann::json &j, const source_location &sl); + +void to_json(nlohmann::json &j, const element &c); + +void to_json(nlohmann::json &j, const template_parameter &c); + +void to_json(nlohmann::json &j, const relationship &c); +} // namespace clanguml::common::model + namespace clanguml::common::generators::json { using clanguml::common::model::access_t; diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc index 3dfab613..d321102c 100644 --- a/src/common/model/enums.cc +++ b/src/common/model/enums.cc @@ -136,6 +136,19 @@ std::string to_string(const diagram_t t) } } +std::string to_string(const message_scope_t t) +{ + switch (t) { + case message_scope_t::kNormal: + return "normal"; + case message_scope_t::kCondition: + return "condition"; + default: + assert(false); + return ""; + } +} + diagram_t from_string(const std::string &s) { if (s == "class") diff --git a/src/common/model/enums.h b/src/common/model/enums.h index 3ec183c4..633a236f 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -80,6 +80,8 @@ std::string to_string(message_t m); std::string to_string(diagram_t r); +std::string to_string(message_scope_t); + diagram_t from_string(const std::string &s); } // namespace clanguml::common::model diff --git a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc new file mode 100644 index 00000000..1313d150 --- /dev/null +++ b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc @@ -0,0 +1,546 @@ +/** + * src/sequence_diagram/generators/json/sequence_diagram_generator.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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. + */ + +#include "sequence_diagram_generator.h" + +namespace clanguml::sequence_diagram::model { +using nlohmann::json; + +void to_json(nlohmann::json &j, const participant &c) +{ + j["name"] = c.full_name(false); + j["id"] = std::to_string(c.id()); + j["type"] = c.type_name(); + if (!c.file().empty()) + j["source_location"] = + dynamic_cast(c); + if (const auto &comment = c.comment(); comment) + j["comment"] = comment.value(); +} + +void to_json(nlohmann::json &j, const activity &c) +{ + j["participant_id"] = std::to_string(c.from()); +} + +} // namespace clanguml::class_diagram::model + +namespace clanguml::sequence_diagram::generators::json { + +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; + +// +// generator +// + +generator::generator( + clanguml::config::sequence_diagram &config, diagram_model &model) + : common_generator{config, model} +{ +} + +std::string generator::render_name(std::string name) const +{ + util::replace_all(name, "##", "::"); + + return name; +} + +void generator::generate_call(const message &m, nlohmann::json &parent) const +{ + const auto &from = m_model.get_participant(m.from()); + const auto &to = m_model.get_participant(m.to()); + + if (!from || !to) { + LOG_DBG("Skipping empty call from '{}' to '{}'", m.from(), m.to()); + return; + } + + generate_participant(json_, m.from()); + generate_participant(json_, m.to()); + + std::string message; + + model::function::message_render_mode render_mode = + model::function::message_render_mode::full; + + if (to.value().type_name() == "method") { + message = dynamic_cast(to.value()) + .message_name(render_mode); + } + else if (m_config.combine_free_functions_into_file_participants()) { + if (to.value().type_name() == "function") { + message = dynamic_cast(to.value()) + .message_name(render_mode); + } + else if (to.value().type_name() == "function_template") { + message = dynamic_cast(to.value()) + .message_name(render_mode); + } + } + // + // const std::string from_alias = generate_alias(from.value()); + // const std::string to_alias = generate_alias(to.value()); + + nlohmann::json msg; + + msg["name"] = message; + msg["type"] = "message"; + msg["from"]["name"] = from.value().full_name(false); + msg["from"]["id"] = std::to_string(from.value().id()); + msg["to"]["activity_id"] = std::to_string(to.value().id()); + msg["to"]["activity_name"] = to.value().full_name(false); + + if (to.value().type_name() == "method") { + const auto &class_participant = + m_model.get_participant(to.value().id()).value(); + msg["to"]["participant_id"] = + std::to_string(class_participant.class_id()); + msg["to"]["participant_name"] = + m_model.get_participant(class_participant.class_id()) + .value() + .full_name(false); + } + else if (to.value().type_name() == "function" && + m_config.combine_free_functions_into_file_participants()) { + const auto &file_participant = + m_model.get_participant(to.value().id()).value(); + msg["to"]["participant_id"] = + std::to_string(common::to_id(file_participant.file())); + msg["to"]["participant_name"] = file_participant.file_relative(); + } + + msg["source_location"] = + dynamic_cast(m); + + msg["scope"] = to_string(m.message_scope()); + msg["return_type"] = m.return_type(); + + parent["messages"].push_back(std::move(msg)); + + LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from, + m.from(), to, m.to()); +} + +void generator::generate_activity( + common::model::diagram_element::id_t activity_id, const activity &a, + nlohmann::json &unused, + std::vector &visited, + std::optional nested_block) const +{ + // Generate calls from this activity to other activities + for (const auto &m : a.messages()) { + if (m.type() == message_t::kCall) { + const auto &to = + m_model.get_participant(m.to()); + if (!to || to.value().skip()) + continue; + + visited.push_back(m.from()); + + LOG_DBG("Generating message {} --> {}", m.from(), m.to()); + + generate_call(m, current_block_statement()); + + if (m_model.sequences().find(m.to()) != m_model.sequences().end()) { + if (std::find(visited.begin(), visited.end(), m.to()) == + visited + .end()) { // break infinite recursion on recursive calls + + LOG_DBG("Creating activity {} --> {} - missing sequence {}", + m.from(), m.to(), m.to()); + + generate_activity(m.to(), m_model.get_activity(m.to()), + current_block_statement(), visited, {}); + } + } + else + LOG_DBG("Skipping activity {} --> {} - missing sequence {}", + m.from(), m.to(), m.to()); + } + else if (m.type() == message_t::kIf) { + nlohmann::json if_block; + if_block["type"] = "alt"; + if_block["name"] = "if"; + if_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(if_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + + nlohmann::json branch; + branch["type"] = "consequent"; + current_block_statement()["branches"].push_back(std::move(branch)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["branches"].back())); + } + else if (m.type() == message_t::kElseIf || + m.type() == message_t::kElse) { + // remove previous branch from the stack + block_statements_stack_.pop_back(); + + nlohmann::json branch; + branch["type"] = "alternative"; + current_block_statement()["branches"].push_back(std::move(branch)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["branches"].back())); + } + else if (m.type() == message_t::kIfEnd) { + // Remove last if branch from the stack + block_statements_stack_.pop_back(); + + // Remove the if statement block from the stack + block_statements_stack_.pop_back(); + } + else if (m.type() == message_t::kWhile) { + nlohmann::json while_block; + while_block["type"] = "loop"; + while_block["name"] = "while"; + while_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(while_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + } + else if (m.type() == message_t::kWhileEnd) { + // Remove the while statement block from the stack + block_statements_stack_.pop_back(); + } + else if (m.type() == message_t::kFor) { + nlohmann::json for_block; + for_block["type"] = "loop"; + for_block["name"] = "for"; + for_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(for_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + } + else if (m.type() == message_t::kForEnd) { + // Remove the while statement block from the stack + block_statements_stack_.pop_back(); + } + else if (m.type() == message_t::kDo) { + nlohmann::json do_block; + do_block["type"] = "loop"; + do_block["name"] = "do"; + do_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(do_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + } + else if (m.type() == message_t::kDoEnd) { + // Remove the do statement block from the stack + block_statements_stack_.pop_back(); + } + else if (m.type() == message_t::kTry) { + nlohmann::json try_block; + try_block["type"] = "break"; + try_block["name"] = "try"; + try_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(try_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + + nlohmann::json branch; + branch["type"] = "main"; + current_block_statement()["blocks"].push_back(std::move(branch)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["blocks"].back())); + } + else if (m.type() == message_t::kCatch) { + // remove previous block from the stack + block_statements_stack_.pop_back(); + + nlohmann::json branch; + branch["type"] = "catch"; + current_block_statement()["blocks"].push_back(std::move(branch)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["blocks"].back())); + } + else if (m.type() == message_t::kTryEnd) { + // Remove last if block from the stack + block_statements_stack_.pop_back(); + + // Remove the try statement block from the stack + block_statements_stack_.pop_back(); + } + else if (m.type() == message_t::kSwitch) { + nlohmann::json if_block; + if_block["type"] = "alt"; + if_block["name"] = "switch"; + if_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(if_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + } + else if (m.type() == message_t::kCase) { + if (current_block_statement()["type"] == "case") + block_statements_stack_.pop_back(); + + nlohmann::json case_block; + case_block["type"] = "case"; + case_block["name"] = m.message_name(); + current_block_statement()["cases"].push_back(std::move(case_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["cases"].back())); + } + else if (m.type() == message_t::kSwitchEnd) { + // Remove last case block from the stack + block_statements_stack_.pop_back(); + + // Remove the switch statement block from the stack + block_statements_stack_.pop_back(); + } + else if (m.type() == message_t::kConditional) { + nlohmann::json if_block; + if_block["type"] = "alt"; + if_block["name"] = "conditional"; + if_block["activity_id"] = std::to_string(m.from()); + + current_block_statement()["messages"].push_back( + std::move(if_block)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["messages"].back())); + + nlohmann::json branch; + branch["type"] = "consequent"; + current_block_statement()["branches"].push_back(std::move(branch)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["branches"].back())); + } + else if (m.type() == message_t::kElse) { + // remove previous branch from the stack + block_statements_stack_.pop_back(); + + nlohmann::json branch; + branch["type"] = "alternative"; + current_block_statement()["branches"].push_back(std::move(branch)); + + block_statements_stack_.push_back( + std::ref(current_block_statement()["branches"].back())); + } + else if (m.type() == message_t::kConditionalEnd) { + // Remove last if branch from the stack + block_statements_stack_.pop_back(); + + // Remove the if statement block from the stack + block_statements_stack_.pop_back(); + } + else { + // Unhandled message_t case + assert(false); + } + } +} + +void generator::generate_participant( + nlohmann::json &parent, const std::string &name) const +{ + auto p = m_model.get(name); + + if (!p.has_value()) { + LOG_WARN("Cannot find participant {} from `participants_order` option", + name); + return; + } + + generate_participant(parent, p.value().id(), true); +} + +common::id_t generator::generate_participant( + nlohmann::json &parent, common::id_t id, bool force) const +{ + common::id_t participant_id{0}; + + if (!force) { + for (const auto pid : m_model.active_participants()) { + if (pid == id) { + participant_id = pid; + break; + } + } + } + else + participant_id = id; + + if (participant_id == 0) + return participant_id; + + if (is_participant_generated(participant_id)) + return participant_id; + + const auto &participant = + m_model.get_participant(participant_id).value(); + + if (participant.type_name() == "method") { + const auto class_id = + m_model.get_participant(participant_id) + .value() + .class_id(); + + if (is_participant_generated(class_id)) + return participant_id; + + const auto &class_participant = + m_model.get_participant(class_id).value(); + + parent["participants"].push_back(class_participant); + + generated_participants_.emplace(class_id); + } + else if ((participant.type_name() == "function" || + participant.type_name() == "function_template") && + m_config.combine_free_functions_into_file_participants()) { + // Create a single participant for all functions declared in a + // single file + const auto &function_participant = + m_model.get_participant(participant_id).value(); + + parent["participants"].push_back(function_participant); + + generated_participants_.emplace( + common::to_id(function_participant.file())); + } + else { + parent["participants"].push_back(participant); + + generated_participants_.emplace(participant_id); + } + + return participant_id; +} + +bool generator::is_participant_generated(common::id_t id) const +{ + return std::find(generated_participants_.begin(), + generated_participants_.end(), + id) != generated_participants_.end(); +} + +void generator::generate(std::ostream &ostr) const +{ + m_model.print(); + + json_["name"] = m_model.name(); + json_["diagram_type"] = "sequence"; + if (m_config.using_namespace) + json_["using_namespace"] = m_config.using_namespace().to_string(); + + if (m_config.participants_order.has_value) { + for (const auto &p : m_config.participants_order()) { + LOG_DBG("Pregenerating participant {}", p); + generate_participant(json_, p); + } + } + + for (const auto &sf : m_config.start_from()) { + if (sf.location_type == location_t::function) { + common::model::diagram_element::id_t start_from{0}; + std::string start_from_str; + for (const auto &[k, v] : m_model.sequences()) { + const auto &caller = *m_model.participants().at(v.from()); + std::string vfrom = caller.full_name(false); + if (vfrom == sf.location) { + LOG_DBG("Found sequence diagram start point: {}", k); + start_from = k; + break; + } + } + + if (start_from == 0) { + LOG_WARN("Failed to find participant with {} for start_from " + "condition", + sf.location); + continue; + } + + // Use this to break out of recurrent loops + std::vector + visited_participants; + + const auto &from = + m_model.get_participant(start_from); + + if (!from.has_value()) { + LOG_WARN("Failed to find participant {} for start_from " + "condition", + sf.location); + continue; + } + + generate_participant(json_, start_from); + + [[maybe_unused]] model::function::message_render_mode render_mode = + model::function::message_render_mode::full; + + nlohmann::json sequence; + sequence["start_from"]["location"] = sf.location; + sequence["start_from"]["id"] = start_from; + + block_statements_stack_.push_back(std::ref(sequence)); + + generate_activity(start_from, m_model.get_activity(start_from), + sequence, visited_participants, {}); + + json_["sequences"].push_back(std::move(sequence)); + + block_statements_stack_.pop_back(); + + if (from.value().type_name() == "method" || + m_config.combine_free_functions_into_file_participants()) { + + // TODO + // sequence["return_type"] = from.value().id() + } + } + else { + // TODO: Add support for other sequence start location types + continue; + } + } + + ostr << json_; +} +} // namespace clanguml::sequence_diagram::generators::plantuml diff --git a/src/sequence_diagram/generators/json/sequence_diagram_generator.h b/src/sequence_diagram/generators/json/sequence_diagram_generator.h new file mode 100644 index 00000000..bb383b0c --- /dev/null +++ b/src/sequence_diagram/generators/json/sequence_diagram_generator.h @@ -0,0 +1,81 @@ +/** + * src/sequence_diagram/generators/json/sequence_diagram_generator.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * 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. + */ +#pragma once + +#include "common/generators/json/generator.h" +#include "config/config.h" +#include "sequence_diagram/model/diagram.h" +#include "util/util.h" + +#include + +#include +#include +#include +#include +#include + +namespace clanguml::sequence_diagram::generators::json { + +using diagram_config = clanguml::config::sequence_diagram; +using diagram_model = clanguml::sequence_diagram::model::diagram; + +template +using common_generator = clanguml::common::generators::json::generator; + +class generator : public common_generator { +public: + generator(diagram_config &config, diagram_model &model); + + void generate_call(const sequence_diagram::model::message &m, + nlohmann::json &parent) const; + + common::id_t generate_participant( + nlohmann::json &parent, common::id_t id, bool force = false) const; + + void generate_participant( + nlohmann::json &parent, const std::string &name) const; + + void generate_activity(common::model::diagram_element::id_t activity_id, + const sequence_diagram::model::activity &a, nlohmann::json &parent, + std::vector &visited, + std::optional nested_block) const; + + void generate(std::ostream &ostr) const override; + + nlohmann::json ¤t_block_statement() const + { + assert(!block_statements_stack_.empty()); + + return block_statements_stack_.back().get(); + } + +private: + bool is_participant_generated(common::id_t id) const; + + std::string render_name(std::string name) const; + + mutable std::set generated_participants_; + + mutable nlohmann::json json_; + + mutable std::vector> + block_statements_stack_; +}; + +} // namespace clanguml::sequence_diagram::generators::json diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 0695d68d..a0a3b5bc 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -433,6 +433,10 @@ void generator::generate(std::ostream &ostr) const render_mode = model::function::message_render_mode::no_arguments; + // For methods or functions in diagrams where they are combined into + // file participants, we need to add an 'entry' point call to know + // which method relates to the first activity for this 'start_from' + // condition if (from.value().type_name() == "method" || m_config.combine_free_functions_into_file_participants()) { ostr << "[->" diff --git a/tests/t20001/test_case.h b/tests/t20001/test_case.h index b14e887a..19d1694d 100644 --- a/tests/t20001/test_case.h +++ b/tests/t20001/test_case.h @@ -47,4 +47,20 @@ TEST_CASE("t20001", "[test-case][sequence]") REQUIRE_THAT(puml, HasComment("t20001 test diagram of type sequence")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + REQUIRE(j["participants"][0]["name"] == "clanguml::t20001::tmain()"); + REQUIRE(j["participants"][1]["name"] == "clanguml::t20001::A"); + REQUIRE(j["participants"][2]["name"] == "clanguml::t20001::B"); + + auto &messages = j["sequences"][0]["messages"]; + REQUIRE(messages[0]["name"] == "add(int,int)"); + REQUIRE(messages[1]["name"] == "wrap_add3(int,int,int)"); + REQUIRE(messages[2]["name"] == "add3(int,int,int)"); + REQUIRE(messages[3]["name"] == "add(int,int)"); + REQUIRE(messages[4]["name"] == "__log_result(int)__"); + REQUIRE(messages[5]["name"] == "__log_result(int)__"); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } diff --git a/tests/t20002/test_case.h b/tests/t20002/test_case.h index 39e7a1dd..1008c217 100644 --- a/tests/t20002/test_case.h +++ b/tests/t20002/test_case.h @@ -39,4 +39,10 @@ TEST_CASE("t20002", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("m3()"), _A("m4()"), "")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } diff --git a/tests/t20003/test_case.h b/tests/t20003/test_case.h index 46e3d2db..cbd0b78e 100644 --- a/tests/t20003/test_case.h +++ b/tests/t20003/test_case.h @@ -39,4 +39,10 @@ TEST_CASE("t20003", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("m3(T)"), _A("m4(T)"), "")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } diff --git a/tests/t20004/test_case.h b/tests/t20004/test_case.h index de1ea0cd..14ff035a 100644 --- a/tests/t20004/test_case.h +++ b/tests/t20004/test_case.h @@ -57,4 +57,10 @@ TEST_CASE("t20004", "[test-case][sequence]") REQUIRE_THAT(puml, EndsWith("@enduml\n")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20005/test_case.h b/tests/t20005/test_case.h index c5d65d87..3517260b 100644 --- a/tests/t20005/test_case.h +++ b/tests/t20005/test_case.h @@ -40,4 +40,10 @@ TEST_CASE("t20005", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a(T)")); REQUIRE_THAT(puml, HasExitpoint(_A("C"))); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20006/test_case.h b/tests/t20006/test_case.h index 8280336f..534af040 100644 --- a/tests/t20006/test_case.h +++ b/tests/t20006/test_case.h @@ -70,4 +70,10 @@ TEST_CASE("t20006", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("BB"), _A("AA"), "aa2(int)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20007/test_case.h b/tests/t20007/test_case.h index 504000cd..8a3d9fc3 100644 --- a/tests/t20007/test_case.h +++ b/tests/t20007/test_case.h @@ -45,4 +45,10 @@ TEST_CASE("t20007", "[test-case][sequence]") "add(std::string &&,std::string &&,std::string &&)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20008/test_case.h b/tests/t20008/test_case.h index 663de6f7..2ee3fc37 100644 --- a/tests/t20008/test_case.h +++ b/tests/t20008/test_case.h @@ -52,4 +52,10 @@ TEST_CASE("t20008", "[test-case][sequence]") HasCall(_A("B"), _A("A"), "a3(std::string)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20009/test_case.h b/tests/t20009/test_case.h index 97dd7f83..cc284870 100644 --- a/tests/t20009/test_case.h +++ b/tests/t20009/test_case.h @@ -46,4 +46,10 @@ TEST_CASE("t20009", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b(float)")); REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a(float)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20010/test_case.h b/tests/t20010/test_case.h index 613cfd44..6e01b636 100644 --- a/tests/t20010/test_case.h +++ b/tests/t20010/test_case.h @@ -48,4 +48,10 @@ TEST_CASE("t20010", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a4()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20011/test_case.h b/tests/t20011/test_case.h index 0fd58f8c..dd4f623e 100644 --- a/tests/t20011/test_case.h +++ b/tests/t20011/test_case.h @@ -44,4 +44,10 @@ TEST_CASE("t20011", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "b(int)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20012/test_case.h b/tests/t20012/test_case.h index b47b0954..f76ba804 100644 --- a/tests/t20012/test_case.h +++ b/tests/t20012/test_case.h @@ -77,4 +77,10 @@ TEST_CASE("t20012", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("D"), "add5(int)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20013/test_case.h b/tests/t20013/test_case.h index f661971e..5b92f91a 100644 --- a/tests/t20013/test_case.h +++ b/tests/t20013/test_case.h @@ -46,4 +46,10 @@ TEST_CASE("t20013", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a3(const char *)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20014/test_case.h b/tests/t20014/test_case.h index f7d7aeb9..85c13ba8 100644 --- a/tests/t20014/test_case.h +++ b/tests/t20014/test_case.h @@ -45,4 +45,10 @@ TEST_CASE("t20014", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b1(int,int)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20015/test_case.h b/tests/t20015/test_case.h index 0794f8b4..1bc01504 100644 --- a/tests/t20015/test_case.h +++ b/tests/t20015/test_case.h @@ -47,4 +47,10 @@ TEST_CASE("t20015", "[test-case][sequence]") REQUIRE_THAT(puml, !HasCall(_A("B"), _A("B"), "set_z(int)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20016/test_case.h b/tests/t20016/test_case.h index a2f2c3f5..03967382 100644 --- a/tests/t20016/test_case.h +++ b/tests/t20016/test_case.h @@ -42,4 +42,10 @@ TEST_CASE("t20016", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2(const long &)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20017/test_case.h b/tests/t20017/test_case.h index b48821d2..101741ed 100644 --- a/tests/t20017/test_case.h +++ b/tests/t20017/test_case.h @@ -49,4 +49,10 @@ TEST_CASE("t20017", "[test-case][sequence]") REQUIRE_THAT(puml, HasExitpoint(_A("t20017.cc"))); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20018/test_case.h b/tests/t20018/test_case.h index 675441ef..82771236 100644 --- a/tests/t20018/test_case.h +++ b/tests/t20018/test_case.h @@ -52,4 +52,10 @@ TEST_CASE("t20018", "[test-case][sequence]") HasCall(_A("Factorial<1>"), _A("Factorial<0>"), "__print(int)__")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20019/test_case.h b/tests/t20019/test_case.h index 73eb87a8..7276929f 100644 --- a/tests/t20019/test_case.h +++ b/tests/t20019/test_case.h @@ -41,4 +41,10 @@ TEST_CASE("t20019", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("Base"), _A("D2"), "impl()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20020/test_case.h b/tests/t20020/test_case.h index 21beae59..0c995955 100644 --- a/tests/t20020/test_case.h +++ b/tests/t20020/test_case.h @@ -51,4 +51,10 @@ TEST_CASE("t20020", "[test-case][sequence]") puml, HasCallInControlCondition(_A("tmain()"), _A("C"), "c3(int)")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20021/test_case.h b/tests/t20021/test_case.h index 61e0c84f..5e68aea7 100644 --- a/tests/t20021/test_case.h +++ b/tests/t20021/test_case.h @@ -58,4 +58,10 @@ TEST_CASE("t20021", "[test-case][sequence]") puml, HasCallInControlCondition(_A("tmain()"), _A("C"), "contents()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20022/test_case.h b/tests/t20022/test_case.h index 6a12c019..56c81b26 100644 --- a/tests/t20022/test_case.h +++ b/tests/t20022/test_case.h @@ -39,4 +39,10 @@ TEST_CASE("t20022", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("A"), _A("B"), "b()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20023/test_case.h b/tests/t20023/test_case.h index 71e3b0bc..9a66753a 100644 --- a/tests/t20023/test_case.h +++ b/tests/t20023/test_case.h @@ -42,4 +42,10 @@ TEST_CASE("t20023", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a4()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20024/test_case.h b/tests/t20024/test_case.h index 5c432062..e049cd0b 100644 --- a/tests/t20024/test_case.h +++ b/tests/t20024/test_case.h @@ -47,4 +47,10 @@ TEST_CASE("t20024", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("B"), _A("B"), "green()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20025/test_case.h b/tests/t20025/test_case.h index 3874cd53..b62945cb 100644 --- a/tests/t20025/test_case.h +++ b/tests/t20025/test_case.h @@ -42,4 +42,10 @@ TEST_CASE("t20025", "[test-case][sequence]") REQUIRE_THAT(puml, !HasCall(_A("tmain()"), _A("add2(int,int)"), "")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20026/test_case.h b/tests/t20026/test_case.h index 934d8ad2..1fa56093 100644 --- a/tests/t20026/test_case.h +++ b/tests/t20026/test_case.h @@ -38,4 +38,10 @@ TEST_CASE("t20026", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20027/test_case.h b/tests/t20027/test_case.h index c8d807a6..410f86af 100644 --- a/tests/t20027/test_case.h +++ b/tests/t20027/test_case.h @@ -40,4 +40,10 @@ TEST_CASE("t20027", "[test-case][sequence]") REQUIRE_THAT(puml, !HasCall(_A("A"), _A("A"), "aaa()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20028/test_case.h b/tests/t20028/test_case.h index 6d97fb09..da0c0e7e 100644 --- a/tests/t20028/test_case.h +++ b/tests/t20028/test_case.h @@ -42,4 +42,10 @@ TEST_CASE("t20028", "[test-case][sequence]") REQUIRE_THAT(puml, !HasCall(_A("tmain()"), _A("B"), "e()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + auto j = generate_sequence_json(diagram, *model); + + // REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/t20029/test_case.h b/tests/t20029/test_case.h index cc63df20..d6e54ee5 100644 --- a/tests/t20029/test_case.h +++ b/tests/t20029/test_case.h @@ -57,4 +57,231 @@ TEST_CASE("t20029", "[test-case][sequence]") !HasCall(_A("ConnectionPool"), _A("ConnectionPool"), "connect_impl()")); save_puml(config.output_directory() + "/" + diagram->name + ".puml", puml); + + const std::string expected_json = R"###( +{ + "diagram_type": "sequence", + "name": "t20029_sequence", + "participants": [ + { + "id": "2091374738808319642", + "name": "clanguml::t20029::tmain()", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 55 + }, + "type": "function" + }, + { + "id": "1673261195873192383", + "name": "clanguml::t20029::Encoder>", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 11 + }, + "type": "class" + }, + { + "id": "658058855590948094", + "name": "clanguml::t20029::Retrier", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 22 + }, + "type": "class" + }, + { + "id": "1896406205097618937", + "name": "clanguml::t20029::ConnectionPool", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 39 + }, + "type": "class" + }, + { + "id": "1362646431260879440", + "name": "clanguml::t20029::encode_b64(std::string &&)", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 9 + }, + "type": "function" + } + ], + "sequences": [ + { + "messages": [ + { + "from": { + "id": "2091374738808319642", + "name": "clanguml::t20029::tmain()" + }, + "name": "connect()", + "return_type": "void", + "scope": "normal", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 59 + }, + "to": { + "activity_id": "940428568182104530", + "activity_name": "clanguml::t20029::ConnectionPool::connect()", + "participant_id": "1896406205097618937", + "participant_name": "clanguml::t20029::ConnectionPool" + }, + "type": "message" + }, + { + "activity_id": "2091374738808319642", + "messages": [ + { + "activity_id": "2091374738808319642", + "branches": [ + { + "messages": [ + { + "from": { + "id": "2091374738808319642", + "name": "clanguml::t20029::tmain()" + }, + "name": "send(std::string &&)", + "return_type": "_Bool", + "scope": "condition", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 62 + }, + "to": { + "activity_id": "2026763864005979273", + "activity_name": "clanguml::t20029::Encoder>::send(std::string &&)", + "participant_id": "1673261195873192383", + "participant_name": "clanguml::t20029::Encoder>" + }, + "type": "message" + }, + { + "from": { + "id": "2026763864005979273", + "name": "clanguml::t20029::Encoder>::send(std::string &&)" + }, + "name": "encode(std::string &&)", + "return_type": "std::string", + "scope": "normal", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 15 + }, + "to": { + "activity_id": "1468258269466480773", + "activity_name": "clanguml::t20029::Encoder>::encode(std::string &&)", + "participant_id": "1673261195873192383", + "participant_name": "clanguml::t20029::Encoder>" + }, + "type": "message" + }, + { + "from": { + "id": "1468258269466480773", + "name": "clanguml::t20029::Encoder>::encode(std::string &&)" + }, + "name": "", + "return_type": "", + "scope": "normal", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 19 + }, + "to": { + "activity_id": "1362646431260879440", + "activity_name": "clanguml::t20029::encode_b64(std::string &&)" + }, + "type": "message" + }, + { + "from": { + "id": "2026763864005979273", + "name": "clanguml::t20029::Encoder>::send(std::string &&)" + }, + "name": "send(std::string &&)", + "return_type": "_Bool", + "scope": "normal", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 15 + }, + "to": { + "activity_id": "30515971485361302", + "activity_name": "clanguml::t20029::Retrier::send(std::string &&)", + "participant_id": "658058855590948094", + "participant_name": "clanguml::t20029::Retrier" + }, + "type": "message" + }, + { + "activity_id": "30515971485361302", + "messages": [ + { + "activity_id": "30515971485361302", + "branches": [ + { + "messages": [ + { + "from": { + "id": "30515971485361302", + "name": "clanguml::t20029::Retrier::send(std::string &&)" + }, + "name": "send(const std::string &)", + "return_type": "_Bool", + "scope": "condition", + "source_location": { + "file": "../../tests/t20029/t20029.cc", + "line": 31 + }, + "to": { + "activity_id": "972625940114169157", + "activity_name": "clanguml::t20029::ConnectionPool::send(const std::string &)", + "participant_id": "1896406205097618937", + "participant_name": "clanguml::t20029::ConnectionPool" + }, + "type": "message" + } + ], + "type": "consequent" + } + ], + "name": "if", + "type": "alt" + } + ], + "name": "while", + "type": "loop" + } + ], + "type": "consequent" + } + ], + "name": "if", + "type": "alt" + } + ], + "name": "for", + "type": "loop" + } + ], + "start_from": { + "id": 2091374738808319700, + "location": "clanguml::t20029::tmain()" + } + } + ], + "using_namespace": "clanguml::t20029" +} +)###"; + + auto j = generate_sequence_json(diagram, *model); + + REQUIRE(j == nlohmann::json::parse(expected_json)); + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); } \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index c7b52c8c..4d3769a0 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -142,6 +142,20 @@ std::string generate_sequence_puml( return ss.str(); } +nlohmann::json generate_sequence_json( + std::shared_ptr config, + clanguml::sequence_diagram::model::diagram &model) +{ + using namespace clanguml::sequence_diagram::generators::json; + + std::stringstream ss; + + ss << generator( + dynamic_cast(*config), model); + + return nlohmann::json::parse(ss.str()); +} + std::string generate_class_puml( std::shared_ptr config, clanguml::class_diagram::model::diagram &model) diff --git a/util/generate_test_cases_docs.py b/util/generate_test_cases_docs.py index 4ec33dfd..bb87efc8 100755 --- a/util/generate_test_cases_docs.py +++ b/util/generate_test_cases_docs.py @@ -68,3 +68,12 @@ with open(r'tests/test_cases.yaml') as f: copyfile(f'debug/tests/puml/{diagram_name}.svg', f'docs/test_cases/{diagram_name}.svg') tc.write(f'![{diagram_name}](./{diagram_name}.svg "{test_case["title"]}")\n') + + tc.write("## Generated JSON models\n") + for diagram_name, _ in config_dict['diagrams'].items(): + if os.path.exists(f'debug/tests/puml/{diagram_name}.json'): + with open(f'debug/tests/puml/{diagram_name}.json') as f: + contents = f.read() + tc.write("```json\n") + tc.write(contents) + tc.write("\n```\n") \ No newline at end of file