diff --git a/src/class_diagram/generators/mermaid/class_diagram_generator.cc b/src/class_diagram/generators/mermaid/class_diagram_generator.cc index 15c66af6..59b69050 100644 --- a/src/class_diagram/generators/mermaid/class_diagram_generator.cc +++ b/src/class_diagram/generators/mermaid/class_diagram_generator.cc @@ -33,6 +33,11 @@ generator::generator(diagram_config &config, diagram_model &model) { } +void generator::generate_diagram_type(std::ostream &ostr) const +{ + ostr << "classDiagram\n"; +} + void generator::generate_alias( const common::model::element &c, std::ostream &ostr) const { @@ -696,8 +701,6 @@ void generator::generate_relationships( void generator::generate_diagram(std::ostream &ostr) const { - ostr << "classDiagram\n"; - generate_top_level_elements(ostr); generate_groups(ostr); diff --git a/src/class_diagram/generators/mermaid/class_diagram_generator.h b/src/class_diagram/generators/mermaid/class_diagram_generator.h index 4a8bd118..0ec125af 100644 --- a/src/class_diagram/generators/mermaid/class_diagram_generator.h +++ b/src/class_diagram/generators/mermaid/class_diagram_generator.h @@ -79,6 +79,13 @@ public: */ void generate_diagram(std::ostream &ostr) const override; + /** + * @brief Generate the diagram type + * + * @param ostr Output stream + */ + void generate_diagram_type(std::ostream &ostr) const override; + /** * @brief In a nested diagram, generate the top level elements. * diff --git a/src/common/generators/mermaid/generator.h b/src/common/generators/mermaid/generator.h index 55776f67..6917b968 100644 --- a/src/common/generators/mermaid/generator.h +++ b/src/common/generators/mermaid/generator.h @@ -107,6 +107,16 @@ public: void generate_mermaid_directives( std::ostream &ostr, const std::vector &directives) const; + /** + * @brief Generate the diagram type + * + * This method must be overriden for each diagram type (e.g. it renders + * a single line `classDiagram` for Mermaid class diagrams. + * + * @param ostr Output stream + */ + virtual void generate_diagram_type(std::ostream &ostr) const = 0; + /** * @brief Generate diagram notes * @@ -228,13 +238,13 @@ void generator::generate(std::ostream &ostr) const update_context(); - // generate_mermaid_diagram_type(ostr, config); + generate_diagram_type(ostr); - generate_mermaid_directives(ostr, config.puml().before); + generate_mermaid_directives(ostr, config.mermaid().before); generate_diagram(ostr); - generate_mermaid_directives(ostr, config.puml().after); + generate_mermaid_directives(ostr, config.mermaid().after); generate_metadata(ostr); } @@ -304,6 +314,61 @@ template void generator::generate_mermaid_directives( std::ostream &ostr, const std::vector &directives) const { + + const auto &config = generators::generator::config(); + const auto &model = generators::generator::model(); + + using common::model::namespace_; + + for (const auto &d : directives) { + try { + // Render the directive with template engine first + std::string directive{env().render(std::string_view{d}, context())}; + + // Now search for alias `@A()` directives in the text + // (this is deprecated) + std::tuple alias_match; + while (util::find_element_alias(directive, alias_match)) { + const auto full_name = + config.using_namespace() | std::get<0>(alias_match); + auto element_opt = model.get(full_name.to_string()); + + if (element_opt) + directive.replace(std::get<1>(alias_match), + std::get<2>(alias_match), element_opt.value().alias()); + else { + LOG_ERROR("Cannot find clang-uml alias for element {}", + full_name.to_string()); + directive.replace(std::get<1>(alias_match), + std::get<2>(alias_match), "UNKNOWN_ALIAS"); + } + } + + ostr << indent(1) << directive << '\n'; + } + catch (const clanguml::error::uml_alias_missing &e) { + LOG_ERROR( + "Failed to render MermaidJS directive due to unresolvable " + "alias: {}", + e.what()); + } + catch (const inja::json::parse_error &e) { + LOG_ERROR("Failed to parse Jinja template: {}", d); + } + catch (const inja::json::exception &e) { + LOG_ERROR("Failed to render MermaidJS directive: \n{}\n due to: {}", + d, e.what()); + } + catch (const std::regex_error &e) { + LOG_ERROR("Failed to render MermaidJS directive: \n{}\n due to " + "std::regex_error: {}", + d, e.what()); + } + catch (const std::exception &e) { + LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", + d, e.what()); + } + } } template diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 1c0a9f45..c281f02b 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -42,6 +42,7 @@ using clanguml::config::include_diagram; using clanguml::config::layout_hint; using clanguml::config::location_t; using clanguml::config::member_order_t; +using clanguml::config::mermaid; using clanguml::config::method_arguments; using clanguml::config::method_type; using clanguml::config::package_diagram; @@ -389,6 +390,18 @@ template <> struct convert { } }; +template <> struct convert { + static bool decode(const Node &node, mermaid &rhs) + { + if (node["before"]) + rhs.before = node["before"].as(); + + if (node["after"]) + rhs.after = node["after"].as(); + return true; + } +}; + template <> struct convert { static bool decode(const Node &node, string_or_regex &rhs) { @@ -531,6 +544,7 @@ template bool decode_diagram(const Node &node, T &rhs) get_option(node, rhs.include); get_option(node, rhs.exclude); get_option(node, rhs.puml); + get_option(node, rhs.mermaid); get_option(node, rhs.git); get_option(node, rhs.generate_links); get_option(node, rhs.type_aliases); @@ -762,6 +776,7 @@ template <> struct convert { get_option(node, rhs.remove_compile_flags); get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.puml); + get_option(node, rhs.mermaid); get_option(node, rhs.generate_method_arguments); get_option(node, rhs.generate_packages); get_option(node, rhs.package_type); diff --git a/src/include_diagram/generators/mermaid/include_diagram_generator.cc b/src/include_diagram/generators/mermaid/include_diagram_generator.cc index 4c06e1ce..48cf3472 100644 --- a/src/include_diagram/generators/mermaid/include_diagram_generator.cc +++ b/src/include_diagram/generators/mermaid/include_diagram_generator.cc @@ -29,6 +29,11 @@ generator::generator(diagram_config &config, diagram_model &model) { } +void generator::generate_diagram_type(std::ostream &ostr) const +{ + ostr << "flowchart\n"; +} + void generator::generate_relationships( const source_file &f, std::ostream &ostr) const { @@ -114,8 +119,6 @@ void generator::generate_notes( 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); diff --git a/src/include_diagram/generators/mermaid/include_diagram_generator.h b/src/include_diagram/generators/mermaid/include_diagram_generator.h index 12afe076..4efaf94e 100644 --- a/src/include_diagram/generators/mermaid/include_diagram_generator.h +++ b/src/include_diagram/generators/mermaid/include_diagram_generator.h @@ -64,6 +64,13 @@ public: */ void generate_diagram(std::ostream &ostr) const override; + /** + * @brief Generate the diagram type + * + * @param ostr Output stream + */ + void generate_diagram_type(std::ostream &ostr) const override; + /** * @brief Generate relationships originating from source_file `f` * diff --git a/src/package_diagram/generators/mermaid/package_diagram_generator.cc b/src/package_diagram/generators/mermaid/package_diagram_generator.cc index 8843ce64..1b68d131 100644 --- a/src/package_diagram/generators/mermaid/package_diagram_generator.cc +++ b/src/package_diagram/generators/mermaid/package_diagram_generator.cc @@ -30,6 +30,11 @@ generator::generator(diagram_config &config, diagram_model &model) { } +void generator::generate_diagram_type(std::ostream &ostr) const +{ + ostr << "flowchart\n"; +} + void generator::generate_relationships( const package &p, std::ostream &ostr) const { @@ -144,8 +149,6 @@ void generator::generate_notes( 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)) { diff --git a/src/package_diagram/generators/mermaid/package_diagram_generator.h b/src/package_diagram/generators/mermaid/package_diagram_generator.h index c7d07e68..e17ca55c 100644 --- a/src/package_diagram/generators/mermaid/package_diagram_generator.h +++ b/src/package_diagram/generators/mermaid/package_diagram_generator.h @@ -66,6 +66,13 @@ public: */ void generate_diagram(std::ostream &ostr) const override; + /** + * @brief Generate the diagram type + * + * @param ostr Output stream + */ + void generate_diagram_type(std::ostream &ostr) const override; + /** * @brief Generate relationships originating from package `p` * diff --git a/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc b/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc index 229b3b54..65870510 100644 --- a/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.cc @@ -48,6 +48,11 @@ generator::generator( { } +void generator::generate_diagram_type(std::ostream &ostr) const +{ + ostr << "sequenceDiagram\n"; +} + void generator::generate_call(const message &m, std::ostream &ostr) const { const auto &from = model().get_participant(m.from()); @@ -389,8 +394,6 @@ void generator::generate_diagram(std::ostream &ostr) const { model().print(); - ostr << "sequenceDiagram\n"; - if (config().participants_order.has_value) { for (const auto &p : config().participants_order()) { LOG_DBG("Pregenerating participant {}", p); diff --git a/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.h b/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.h index 823f729f..c6bbd33d 100644 --- a/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.h +++ b/src/sequence_diagram/generators/mermaid/sequence_diagram_generator.h @@ -60,6 +60,13 @@ public: */ void generate_diagram(std::ostream &ostr) const override; + /** + * @brief Generate the diagram type + * + * @param ostr Output stream + */ + void generate_diagram_type(std::ostream &ostr) const override; + /** * @brief Generate sequence diagram message. * diff --git a/tests/t20006/test_case.h b/tests/t20006/test_case.h index 625341ae..d2913f07 100644 --- a/tests/t20006/test_case.h +++ b/tests/t20006/test_case.h @@ -96,4 +96,10 @@ TEST_CASE("t20006", "[test-case][sequence]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_sequence_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } \ No newline at end of file diff --git a/tests/t20012/test_case.h b/tests/t20012/test_case.h index fe1b70c6..2b9f099f 100644 --- a/tests/t20012/test_case.h +++ b/tests/t20012/test_case.h @@ -121,4 +121,10 @@ TEST_CASE("t20012", "[test-case][sequence]") save_json(config.output_directory(), diagram->name + ".json", j); } + + { + auto mmd = generate_sequence_mermaid(diagram, *model); + + save_mermaid(config.output_directory(), diagram->name + ".mmd", mmd); + } } diff --git a/tests/t90000/.clang-uml b/tests/t90000/.clang-uml index e09671a3..7f12a85b 100644 --- a/tests/t90000/.clang-uml +++ b/tests/t90000/.clang-uml @@ -18,3 +18,15 @@ diagrams: - 'class "Boo" as C_002' - 'class C_002 {' - '}' + mermaid: + before: + - 'class C_001["Foo"]' + - 'class C_001 {' + - ' +int value' + - '}' + - 'C_001 <|-- ArrayList' + - 'note for C_001 "This is a very important class."' + - 'note "This is a\nfloating note"' + - 'class C_002["Boo"]' + - 'class C_002 {' + - '}' diff --git a/tests/t90000/test_case.h b/tests/t90000/test_case.h index dda3d671..84d50e58 100644 --- a/tests/t90000/test_case.h +++ b/tests/t90000/test_case.h @@ -26,14 +26,30 @@ TEST_CASE("t90000", "[test-case][config]") REQUIRE(model->name() == "t90000_class"); - auto puml = generate_class_puml(diagram, *model); - AliasMatcher _A(puml); + { + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); - REQUIRE_THAT(puml, StartsWith("@startuml")); - REQUIRE_THAT(puml, EndsWith("@enduml\n")); + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); - REQUIRE_THAT(puml, IsClass(_A("Foo"))); - REQUIRE_THAT(puml, IsClass(_A("Boo"))); + REQUIRE_THAT(puml, IsClass(_A("Foo"))); + REQUIRE_THAT(puml, IsClass(_A("Boo"))); - save_puml(config.output_directory(), diagram->name + ".puml", puml); + save_puml(config.output_directory(), diagram->name + ".puml", puml); + } + + { + auto j = generate_class_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory(), diagram->name + ".json", j); + } + + { + auto mmd = generate_class_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 c5714ad7..b2e6222a 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -282,7 +282,6 @@ void save_puml(const std::string &path, const std::string &filename, const std::string &puml) { std::filesystem::path p{path}; - p /= "puml"; p /= filename; save_diagram(p, puml); } @@ -291,7 +290,6 @@ void save_json(const std::string &path, const std::string &filename, const nlohmann::json &j) { std::filesystem::path p{path}; - p /= "json"; p /= filename; save_diagram(p, j); } @@ -300,7 +298,6 @@ void save_mermaid(const std::string &path, const std::string &filename, const std::string &mmd) { std::filesystem::path p{path}; - p /= "mermaid"; p /= filename; save_diagram(p, mmd); }