diff --git a/src/common/generators/generators.h b/src/common/generators/generators.h index 8c3d29d1..f315c124 100644 --- a/src/common/generators/generators.h +++ b/src/common/generators/generators.h @@ -29,6 +29,7 @@ #include "include_diagram/generators/plantuml/include_diagram_generator.h" #include "indicators/indicators.hpp" #include "package_diagram/generators/json/package_diagram_generator.h" +#include "package_diagram/generators/mermaid/package_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/mermaid/sequence_diagram_generator.h" @@ -174,11 +175,11 @@ struct diagram_generator_t { using type = clanguml::sequence_diagram::generators::mermaid::generator; }; -// template <> -// struct diagram_generator_t { -// using type = clanguml::package_diagram::generators::mermaid::generator; -// }; +template <> +struct diagram_generator_t { + using type = clanguml::package_diagram::generators::mermaid::generator; +}; // template <> // struct diagram_generator_t { diff --git a/src/common/generators/mermaid/generator.h b/src/common/generators/mermaid/generator.h index bff4bfed..1485c6a7 100644 --- a/src/common/generators/mermaid/generator.h +++ b/src/common/generators/mermaid/generator.h @@ -126,7 +126,7 @@ public: * @param ostr Output stream * @param element Element to which the note should be attached */ - void generate_notes( + virtual void generate_notes( std::ostream &ostr, const model::element &element) const; /** diff --git a/src/package_diagram/generators/mermaid/package_diagram_generator.cc b/src/package_diagram/generators/mermaid/package_diagram_generator.cc new file mode 100644 index 00000000..58808244 --- /dev/null +++ b/src/package_diagram/generators/mermaid/package_diagram_generator.cc @@ -0,0 +1,169 @@ +/** + * @file src/package_diagram/generators/mermaid/package_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 "package_diagram_generator.h" + +#include "util/error.h" + +namespace clanguml::package_diagram::generators::mermaid { + +using clanguml::common::generators::mermaid::indent; + +generator::generator(diagram_config &config, diagram_model &model) + : common_generator{config, model} + , together_group_stack_{false} +{ +} + +void generator::generate_relationships( + const package &p, std::ostream &ostr) const +{ + LOG_DBG("Generating relationships for package {}", p.full_name(true)); + + // Generate this packages relationship + if (model().should_include(relationship_t::kDependency)) { + for (const auto &r : p.relationships()) { + std::stringstream relstr; + try { + auto destination = model().to_alias(r.destination()); + if (!destination.empty()) { + relstr << p.alias() << " -.-> " << destination << '\n'; + ostr << indent(1) << relstr.str(); + } + } + catch (error::uml_alias_missing &e) { + LOG_DBG("=== Skipping dependency relation from {} to {} due " + "to: {}", + p.full_name(false), r.destination(), e.what()); + } + } + } + + // Process it's subpackages relationships + for (const auto &subpackage : p) { + generate_relationships( + dynamic_cast(*subpackage), ostr); + } +} + +void generator::generate(const package &p, std::ostream &ostr) const +{ + LOG_DBG("Generating package {}", p.name()); + + together_group_stack_.enter(); + + const auto &uns = config().using_namespace(); + + // Don't generate packages from namespaces filtered out by + // using_namespace + if (!uns.starts_with({p.full_name(false)})) { + ostr << indent(1) << "subgraph " << p.alias() << "[" << p.name() + << "]\n"; + + if (p.is_deprecated()) + ostr << indent(1) << "%% <>"; + } + + for (const auto &subpackage : p) { + auto &pkg = dynamic_cast(*subpackage); + if (model().should_include(pkg)) { + auto together_group = + config().get_together_group(pkg.full_name(false)); + if (together_group) { + together_group_stack_.group_together( + together_group.value(), &pkg); + } + else { + generate(pkg, ostr); + } + } + } + + generate_groups(ostr); + + if (!uns.starts_with({p.full_name(false)})) { + ostr << indent(1) << "end" << '\n'; + } + + generate_notes(ostr, p); + + together_group_stack_.leave(); +} + +void generator::generate_groups(std::ostream &ostr) const +{ + for (const auto &[group_name, group_elements] : + together_group_stack_.get_current_groups()) { + ostr << indent(1) << "%% together group - not rendered in MermaidJS \n"; + + for (auto *pkg : group_elements) { + generate(*pkg, ostr); + } + + ostr << indent(1) << "%% end together group\n"; + } +} + +void generator::generate_notes( + std::ostream &ostr, const common::model::element &element) const +{ + const auto &config = + common_generator::config(); + + for (const auto &decorator : element.decorators()) { + auto note = std::dynamic_pointer_cast(decorator); + if (note && note->applies_to_diagram(config.name)) { + auto note_id_str = fmt::format("N_{}", note_id_++); + + ostr << indent(1) << note_id_str << "(" << note->text << ")\n"; + + ostr << indent(1) << note_id_str << "-.-" << element.alias() + << '\n'; + } + } +} + +void generator::generate_diagram(std::ostream &ostr) const +{ + ostr << "flowchart\n"; + + for (const auto &p : model()) { + auto &pkg = dynamic_cast(*p); + if (model().should_include(pkg)) { + auto together_group = + config().get_together_group(pkg.full_name(false)); + if (together_group) { + together_group_stack_.group_together( + together_group.value(), &pkg); + } + else { + generate(pkg, ostr); + } + } + } + + generate_groups(ostr); + + // Process package relationships + for (const auto &p : model()) { + if (model().should_include(dynamic_cast(*p))) + generate_relationships(dynamic_cast(*p), ostr); + } +} + +} // namespace clanguml::package_diagram::generators::mermaid diff --git a/src/package_diagram/generators/mermaid/package_diagram_generator.h b/src/package_diagram/generators/mermaid/package_diagram_generator.h new file mode 100644 index 00000000..4b186990 --- /dev/null +++ b/src/package_diagram/generators/mermaid/package_diagram_generator.h @@ -0,0 +1,110 @@ +/** + * @file src/package_diagram/generators/mermaid/package_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/mermaid/generator.h" +#include "common/generators/nested_element_stack.h" +#include "common/model/package.h" +#include "common/model/relationship.h" +#include "config/config.h" +#include "package_diagram/model/diagram.h" +#include "package_diagram/visitor/translation_unit_visitor.h" +#include "util/util.h" + +#include +#include +#include +#include + +namespace clanguml { +namespace package_diagram { +namespace generators { +namespace mermaid { + +using diagram_config = clanguml::config::package_diagram; +using diagram_model = clanguml::package_diagram::model::diagram; + +template +using common_generator = clanguml::common::generators::mermaid::generator; + +using clanguml::common::model::access_t; +using clanguml::common::model::package; +using clanguml::common::model::relationship_t; +using namespace clanguml::util; + +/** + * @brief Package diagram MermaidJS generator + */ +class generator : public common_generator { +public: + generator(diagram_config &config, diagram_model &model); + + using common_generator::generate; + + /** + * @brief Main generator method. + * + * This method is called first and coordinates the entire diagram + * generation. + * + * @param ostr Output stream. + */ + void generate_diagram(std::ostream &ostr) const override; + + /** + * @brief Generate relationships originating from package `p` + * + * @param p Diagram element + * @param parent Output stream + */ + void generate_relationships(const package &p, std::ostream &ostr) const; + + /** + * @brief Generate diagram package element + * + * @param p Package diagram element + * @param parent Output stream + */ + void generate(const package &e, std::ostream &ostr) const; + + /** + * @brief Generate package elements grouped using `together` MermaidJS tag + * + * @param ostr Output stream + */ + void generate_groups(std::ostream &ostr) const; + + /** + * @brief Generate notes attached to packages + * + * @param ostr Output stream + * @param element Element with a note + */ + void generate_notes(std::ostream &ostr, + const common::model::element &element) const override; + +private: + mutable unsigned long note_id_{0UL}; + mutable common::generators::nested_element_stack + together_group_stack_; +}; + +} // namespace mermaid +} // namespace generators +} // namespace package_diagram +} // namespace clanguml diff --git a/tests/t30001/test_case.h b/tests/t30001/test_case.h index c6beef2e..50faf08b 100644 --- a/tests/t30001/test_case.h +++ b/tests/t30001/test_case.h @@ -82,4 +82,10 @@ TEST_CASE("t30001", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30002/test_case.h b/tests/t30002/test_case.h index 90b630b5..1ebcd105 100644 --- a/tests/t30002/test_case.h +++ b/tests/t30002/test_case.h @@ -121,4 +121,10 @@ TEST_CASE("t30002", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30003/test_case.h b/tests/t30003/test_case.h index a7ae58c7..f001ca5e 100644 --- a/tests/t30003/test_case.h +++ b/tests/t30003/test_case.h @@ -63,4 +63,10 @@ TEST_CASE("t30003", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30004/test_case.h b/tests/t30004/test_case.h index d4a2e2c8..61c5cd81 100644 --- a/tests/t30004/test_case.h +++ b/tests/t30004/test_case.h @@ -58,4 +58,10 @@ TEST_CASE("t30004", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30005/test_case.h b/tests/t30005/test_case.h index ac1ac61c..3c297da7 100644 --- a/tests/t30005/test_case.h +++ b/tests/t30005/test_case.h @@ -65,4 +65,10 @@ TEST_CASE("t30005", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30006/test_case.h b/tests/t30006/test_case.h index 5a11b407..618a393a 100644 --- a/tests/t30006/test_case.h +++ b/tests/t30006/test_case.h @@ -59,4 +59,10 @@ TEST_CASE("t30006", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30007/test_case.h b/tests/t30007/test_case.h index 9287c03d..249f9a22 100644 --- a/tests/t30007/test_case.h +++ b/tests/t30007/test_case.h @@ -63,4 +63,10 @@ TEST_CASE("t30007", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30008/test_case.h b/tests/t30008/test_case.h index 8ced1931..c737f815 100644 --- a/tests/t30008/test_case.h +++ b/tests/t30008/test_case.h @@ -77,4 +77,10 @@ TEST_CASE("t30008", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t30009/test_case.h b/tests/t30009/test_case.h index 1d36c541..41e2575c 100644 --- a/tests/t30009/test_case.h +++ b/tests/t30009/test_case.h @@ -64,4 +64,10 @@ TEST_CASE("t30009", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } \ No newline at end of file diff --git a/tests/t30010/test_case.h b/tests/t30010/test_case.h index 21f2f277..b2bd5dca 100644 --- a/tests/t30010/test_case.h +++ b/tests/t30010/test_case.h @@ -57,4 +57,10 @@ TEST_CASE("t30010", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } \ No newline at end of file diff --git a/tests/t30011/test_case.h b/tests/t30011/test_case.h index 794934d8..3af06b7a 100644 --- a/tests/t30011/test_case.h +++ b/tests/t30011/test_case.h @@ -57,4 +57,10 @@ TEST_CASE("t30011", "[test-case][package]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_package_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index e760bbab..8fbf3350 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -243,6 +243,14 @@ std::string generate_sequence_mermaid( config, model); } +std::string generate_package_mermaid( + std::shared_ptr config, + clanguml::package_diagram::model::diagram &model) +{ + return detail::generate_diagram_mermaid( + config, model); +} + template void save_diagram(const std::filesystem::path &path, const T &diagram) {