From cfc0a4232054101609893be16c15de871b5eaef6 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 9 Sep 2023 01:46:08 +0200 Subject: [PATCH] Added initial support for MermaidJS include diagrams --- src/common/generators/generators.h | 11 +- .../mermaid/include_diagram_generator.cc | 132 ++++++++++++++++++ .../mermaid/include_diagram_generator.h | 96 +++++++++++++ tests/t40001/.clang-uml | 6 +- tests/t40001/test_case.h | 6 + tests/t40002/test_case.h | 6 + tests/t40003/test_case.h | 6 + tests/test_cases.cc | 8 ++ 8 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 src/include_diagram/generators/mermaid/include_diagram_generator.cc create mode 100644 src/include_diagram/generators/mermaid/include_diagram_generator.h diff --git a/src/common/generators/generators.h b/src/common/generators/generators.h index f315c124..151da4be 100644 --- a/src/common/generators/generators.h +++ b/src/common/generators/generators.h @@ -26,6 +26,7 @@ #include "common/model/diagram_filter.h" #include "config/config.h" #include "include_diagram/generators/json/include_diagram_generator.h" +#include "include_diagram/generators/mermaid/include_diagram_generator.h" #include "include_diagram/generators/plantuml/include_diagram_generator.h" #include "indicators/indicators.hpp" #include "package_diagram/generators/json/package_diagram_generator.h" @@ -180,11 +181,11 @@ struct diagram_generator_t { using type = clanguml::package_diagram::generators::mermaid::generator; }; -// template <> -// struct diagram_generator_t { -// using type = clanguml::include_diagram::generators::mermaid::generator; -// }; +template <> +struct diagram_generator_t { + using type = clanguml::include_diagram::generators::mermaid::generator; +}; /** @} */ /** diff --git a/src/include_diagram/generators/mermaid/include_diagram_generator.cc b/src/include_diagram/generators/mermaid/include_diagram_generator.cc new file mode 100644 index 00000000..1bce4e14 --- /dev/null +++ b/src/include_diagram/generators/mermaid/include_diagram_generator.cc @@ -0,0 +1,132 @@ +/** + * @file src/include_diagram/generators/mermaid/include_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 "include_diagram_generator.h" + +#include "util/error.h" + +namespace clanguml::include_diagram::generators::mermaid { + +using clanguml::common::generators::mermaid::indent; + +generator::generator(diagram_config &config, diagram_model &model) + : common_generator{config, model} +{ +} + +void generator::generate_relationships( + const source_file &f, std::ostream &ostr) const +{ + if (!util::contains(m_generated_aliases, f.alias())) + return; + + LOG_DBG("Generating relationships for file {}", f.full_name(true)); + + namespace mermaid_common = clanguml::common::generators::mermaid; + + if (f.type() == common::model::source_file_t::kDirectory) { + util::for_each(f, [this, &ostr](const auto &file) { + generate_relationships( + dynamic_cast(*file), ostr); + }); + } + else { + util::for_each_if( + f.relationships(), + [this](const auto &r) { + return model().should_include(r.type()) && + util::contains(m_generated_aliases, + model().get(r.destination()).value().alias()); + }, + [&f, &ostr, this](const auto &r) { + ostr << indent(1) << f.alias() << " " + << (r.type() == common::model::relationship_t::kDependency + ? "-.->" + : "-->") + << " " << model().get(r.destination()).value().alias() + << '\n'; + }); + } +} + +void generator::generate(const source_file &f, std::ostream &ostr) const +{ + if (f.type() == common::model::source_file_t::kDirectory) { + LOG_DBG("Generating directory {}", f.name()); + + ostr << indent(1) << "subgraph " << f.alias() << "[" << f.name() + << "]\n"; + + util::for_each(f, [this, &ostr](const auto &file) { + generate(dynamic_cast(*file), ostr); + }); + + ostr << indent(1) << "end" << '\n'; + + m_generated_aliases.emplace(f.alias()); + } + else { + LOG_DBG("Generating file {}", f.name()); + + if (model().should_include(f)) { + ostr << indent(1) << f.alias() << "[" << f.name() << "]\n"; + + m_generated_aliases.emplace(f.alias()); + } + } +} + +void generator::generate_notes( + std::ostream &ostr, const common::model::source_file &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"; + + // Generate files and folders + util::for_each(model(), [this, &ostr](const auto &f) { + generate(dynamic_cast(*f), ostr); + }); + + // Process file include relationships + util::for_each(model(), [this, &ostr](const auto &f) { + generate_relationships(dynamic_cast(*f), ostr); + }); + + // Process file notes + util::for_each(model(), [this, &ostr](const auto &f) { + generate_notes(ostr, dynamic_cast(*f)); + }); +} +} // namespace clanguml::include_diagram::generators::plantuml diff --git a/src/include_diagram/generators/mermaid/include_diagram_generator.h b/src/include_diagram/generators/mermaid/include_diagram_generator.h new file mode 100644 index 00000000..aa30a471 --- /dev/null +++ b/src/include_diagram/generators/mermaid/include_diagram_generator.h @@ -0,0 +1,96 @@ +/** + * @file src/include_diagram/generators/mermaid/include_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/model/package.h" +#include "common/model/relationship.h" +#include "common/model/source_file.h" +#include "config/config.h" +#include "include_diagram/model/diagram.h" +#include "include_diagram/visitor/translation_unit_visitor.h" +#include "util/util.h" + +#include +#include +#include +#include + +namespace clanguml::include_diagram::generators::mermaid { + +using diagram_config = clanguml::config::include_diagram; +using diagram_model = clanguml::include_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 clanguml::common::model::source_file; +using namespace clanguml::util; + +/** + * @brief Include 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 source_file `f` + * + * @param p Diagram element + * @param parent Output stream + */ + void generate_relationships(const source_file &p, std::ostream &ostr) const; + + /** + * @brief Generate notes attached to files + * + * @param ostr Output stream + * @param element Element with a note + */ + void generate_notes( + std::ostream &ostr, const common::model::source_file &element) const; + + /** + * @brief Generate diagram element + * + * @param e Source file diagram element + * @param parent Output stream + */ + void generate(const source_file &e, std::ostream &ostr) const; + +private: + mutable unsigned long note_id_{0UL}; +}; + +} // namespace clanguml::include_diagram::generators::mermaid diff --git a/tests/t40001/.clang-uml b/tests/t40001/.clang-uml index 7400619d..eee88bce 100644 --- a/tests/t40001/.clang-uml +++ b/tests/t40001/.clang-uml @@ -20,4 +20,8 @@ diagrams: - "' t40001 test diagram of type {{ diagram.type }}" after: - 'note right of {{ alias("include/lib1") }}: This is a lib1 include dir' - - 'note right of {{ alias("include/t40001_include1.h") }}: This is a t40001_include1.h include file' \ No newline at end of file + - 'note right of {{ alias("include/t40001_include1.h") }}: This is a t40001_include1.h include file' + mermaid: + after: + - 'N_00001(This is a lib1 include dir)-.-{{ alias("include/lib1") }}' + - 'N_00002(This is a lib1 include dir)-.-{{ alias("include/t40001_include1.h") }}' \ No newline at end of file diff --git a/tests/t40001/test_case.h b/tests/t40001/test_case.h index 9f58a9e1..3905c6f9 100644 --- a/tests/t40001/test_case.h +++ b/tests/t40001/test_case.h @@ -79,4 +79,10 @@ TEST_CASE("t40001", "[test-case][include]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_include_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t40002/test_case.h b/tests/t40002/test_case.h index c57831bb..7f70d770 100644 --- a/tests/t40002/test_case.h +++ b/tests/t40002/test_case.h @@ -110,4 +110,10 @@ TEST_CASE("t40002", "[test-case][include]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_include_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t40003/test_case.h b/tests/t40003/test_case.h index 5a847234..bcadef1a 100644 --- a/tests/t40003/test_case.h +++ b/tests/t40003/test_case.h @@ -74,4 +74,10 @@ TEST_CASE("t40003", "[test-case][include]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_include_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 8fbf3350..c5714ad7 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -251,6 +251,14 @@ std::string generate_package_mermaid( config, model); } +std::string generate_include_mermaid( + std::shared_ptr config, + clanguml::include_diagram::model::diagram &model) +{ + return detail::generate_diagram_mermaid( + config, model); +} + template void save_diagram(const std::filesystem::path &path, const T &diagram) {