diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 05b15466..4d6d3f1b 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -38,16 +38,24 @@ void generator::generate_link( auto context = element_context(e); - if (!config().generate_links().link.empty()) { + auto maybe_link_pattern = get_link_pattern(e); + if (maybe_link_pattern) { + const auto &[link_prefix, link_pattern] = *maybe_link_pattern; + auto ec = element_context(e); + common::generators::make_context_source_relative(ec, link_prefix); + ostr << " [[["; - ostr << env().render( - std::string_view{config().generate_links().link}, context); + ostr << env().render(std::string_view{link_pattern}, context); } - if (!config().generate_links().tooltip.empty()) { + auto maybe_tooltip_pattern = get_tooltip_pattern(e); + + if (maybe_tooltip_pattern) { + const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; + auto ec = element_context(e); + common::generators::make_context_source_relative(ec, tooltip_prefix); ostr << "{"; - ostr << env().render( - std::string_view{config().generate_links().tooltip}, context); + ostr << env().render(std::string_view{tooltip_pattern}, ec); ostr << "}"; } ostr << "]]]"; diff --git a/src/common/generators/generator.h b/src/common/generators/generator.h index 27b548e1..3715c03c 100644 --- a/src/common/generators/generator.h +++ b/src/common/generators/generator.h @@ -17,10 +17,22 @@ */ #pragma once +#include "common/model/source_location.h" +#include "util/error.h" +#include "util/util.h" + +#include + +#include #include +#include +#include namespace clanguml::common::generators { +void make_context_source_relative( + inja::json &context, const std::string &prefix); + /** * @brief Common diagram generator interface * @@ -38,6 +50,8 @@ public: : config_{config} , model_{model} { + init_context(); + init_env(); } virtual ~generator() = default; @@ -68,9 +82,240 @@ public: */ const DiagramType &model() const { return model_; } + template inja::json element_context(const E &e) const; + + std::optional> get_link_pattern( + const common::model::source_location &sl) const + { + if (sl.file_relative().empty()) { + return config().generate_links().get_link_pattern(sl.file()); + } + + return config().generate_links().get_link_pattern("."); + } + + std::optional> get_tooltip_pattern( + const common::model::source_location &sl) const + { + if (sl.file_relative().empty()) { + return config().generate_links().get_tooltip_pattern(sl.file()); + } + + return config().generate_links().get_tooltip_pattern("."); + } + + void init_context(); + + /** + * @brief Update diagram Jinja context + * + * This method updates the diagram context with models properties + * which can be used to render Jinja templates in the diagram (e.g. + * in notes or links) + */ + void update_context() const; + + void init_env(); + + const inja::json &context() const; + + inja::Environment &env() const; + +protected: + mutable inja::json m_context; + mutable inja::Environment m_env; + private: ConfigType &config_; DiagramType &model_; }; +template void generator::init_context() +{ + const auto &config = generators::generator::config(); + + if (config.git) { + m_context["git"]["branch"] = config.git().branch; + m_context["git"]["revision"] = config.git().revision; + m_context["git"]["commit"] = config.git().commit; + m_context["git"]["toplevel"] = config.git().toplevel; + } +} + +template void generator::update_context() const +{ + m_context["diagram"] = model().context(); +} + +template +const inja::json &generator::context() const +{ + return m_context; +} + +template +inja::Environment &generator::env() const +{ + return m_env; +} + +template void generator::init_env() +{ + const auto &model = generators::generator::model(); + const auto &config = generators::generator::config(); + + // + // Add basic string functions to inja environment + // + + // Check if string is empty + m_env.add_callback("empty", 1, [](inja::Arguments &args) { + return args.at(0)->get().empty(); + }); + + // Remove spaces from the left of a string + m_env.add_callback("ltrim", 1, [](inja::Arguments &args) { + return util::ltrim(args.at(0)->get()); + }); + + // Remove trailing spaces from a string + m_env.add_callback("rtrim", 1, [](inja::Arguments &args) { + return util::rtrim(args.at(0)->get()); + }); + + // Remove spaces before and after a string + m_env.add_callback("trim", 1, [](inja::Arguments &args) { + return util::trim(args.at(0)->get()); + }); + + // Make a string shorted with a limit to + m_env.add_callback("abbrv", 2, [](inja::Arguments &args) { + return util::abbreviate( + args.at(0)->get(), args.at(1)->get()); + }); + + m_env.add_callback("replace", 3, [](inja::Arguments &args) { + std::string result = args[0]->get(); + std::regex pattern(args[1]->get()); + return std::regex_replace(result, pattern, args[2]->get()); + }); + + m_env.add_callback("split", 2, [](inja::Arguments &args) { + return util::split( + args[0]->get(), args[1]->get()); + }); + + // + // Add PlantUML specific functions + // + + // Return the entire element JSON context based on element name + // e.g.: + // {{ element("clanguml::t00050::A").comment }} + // + m_env.add_callback("element", 1, [&model, &config](inja::Arguments &args) { + inja::json res{}; + auto element_opt = model.get_with_namespace( + args[0]->get(), config.using_namespace()); + + if (element_opt.has_value()) + res = element_opt.value().context(); + + return res; + }); + + // Convert C++ entity to PlantUML alias, e.g. + // "note left of {{ alias("A") }}: This is a note" + // Shortcut to: + // {{ element("A").alias }} + // + m_env.add_callback("alias", 1, [&model, &config](inja::Arguments &args) { + auto element_opt = model.get_with_namespace( + args[0]->get(), config.using_namespace()); + + if (!element_opt.has_value()) + throw clanguml::error::uml_alias_missing( + args[0]->get()); + + return element_opt.value().alias(); + }); + + // Get elements' comment: + // "note left of {{ alias("A") }}: {{ comment("A") }}" + // Shortcut to: + // {{ element("A").comment }} + // + m_env.add_callback("comment", 1, [&model, &config](inja::Arguments &args) { + inja::json res{}; + auto element_opt = model.get_with_namespace( + args[0]->get(), config.using_namespace()); + + if (!element_opt.has_value()) + throw clanguml::error::uml_alias_missing( + args[0]->get()); + + auto comment = element_opt.value().comment(); + + if (comment.has_value()) { + assert(comment.value().is_object()); + res = comment.value(); + } + + return res; + }); +} + +template +template +inja::json generator::element_context(const E &e) const +{ + const auto &diagram_context = context(); + + inja::json ctx; + ctx["element"] = e.context(); +#if _MSC_VER + if (ctx.contains("git")) { +#else + if (diagram_context.template contains("git")) { +#endif + ctx["git"] = diagram_context["git"]; + } + + if (!e.file().empty()) { + std::filesystem::path file{e.file()}; + std::string git_relative_path = file.string(); + if (!e.file_relative().empty()) { +#if _MSC_VER + if (file.is_absolute() && ctx.contains("git")) { +#else + if (file.is_absolute() && + diagram_context.template contains("git")) { +#endif + git_relative_path = std::filesystem::relative( + file, diagram_context["git"]["toplevel"]) + .string(); + ctx["element"]["source"]["path"] = + util::path_to_url(git_relative_path); + } + else { + ctx["element"]["source"]["path"] = e.file(); + } + } + else { + git_relative_path = ""; + ctx["element"]["source"]["path"] = e.file(); + } + + ctx["element"]["source"]["full_path"] = file.string(); + ctx["element"]["source"]["name"] = file.filename().string(); + ctx["element"]["source"]["line"] = e.line(); + } + + const auto &maybe_comment = e.comment(); + if (maybe_comment) { + ctx["element"]["comment"] = maybe_comment.value(); + } + + return ctx; +} } // namespace clanguml::common::generators \ No newline at end of file diff --git a/src/common/generators/generators.cc b/src/common/generators/generators.cc index 592c6c31..2637d22f 100644 --- a/src/common/generators/generators.cc +++ b/src/common/generators/generators.cc @@ -21,6 +21,28 @@ #include "progress_indicator.h" namespace clanguml::common::generators { +void make_context_source_relative( + inja::json &context, const std::string &prefix) +{ + if (!context.contains("element")) + return; + + if (!context["element"].contains("source")) + return; + + auto &source = context["element"]["source"]; + + if (source.at("path").empty()) + return; + + auto path = std::filesystem::path(source.at("path")); + auto prefix_path = std::filesystem::path(prefix); + if (path.is_absolute() && util::is_relative_to(path, prefix_path)) { + source["path"] = relative(path, prefix_path); + return; + } +} + void find_translation_units_for_diagrams( const std::vector &diagram_names, clanguml::config::config &config, diff --git a/src/common/generators/mermaid/generator.h b/src/common/generators/mermaid/generator.h index f48d6fa8..6a24e2f1 100644 --- a/src/common/generators/mermaid/generator.h +++ b/src/common/generators/mermaid/generator.h @@ -67,8 +67,6 @@ public: : clanguml::common::generators::generator{ config, model} { - init_context(); - init_env(); } ~generator() override = default; @@ -169,84 +167,11 @@ public: */ void print_debug( const common::model::source_location &e, std::ostream &ostr) const; - /** - * @brief Update diagram Jinja context - * - * This method updates the diagram context with models properties - * which can be used to render Jinja templates in the diagram (e.g. - * in notes or links) - */ - void update_context() const; - -protected: - const inja::json &context() const; - - inja::Environment &env() const; - - template inja::json element_context(const E &e) const; - -private: - void init_context(); - - void init_env(); protected: mutable std::set m_generated_aliases; - mutable inja::json m_context; - mutable inja::Environment m_env; }; -template -const inja::json &generator::context() const -{ - return m_context; -} - -template -inja::Environment &generator::env() const -{ - return m_env; -} - -template -template -inja::json generator::element_context(const E &e) const -{ - auto ctx = context(); - - ctx["element"] = e.context(); - - if (!e.file().empty()) { - std::filesystem::path file{e.file()}; - std::string git_relative_path = file.string(); - if (!e.file_relative().empty()) { -#if _MSC_VER - if (file.is_absolute() && ctx.contains("git")) -#else - if (file.is_absolute() && ctx.template contains("git")) -#endif - git_relative_path = - std::filesystem::relative(file, ctx["git"]["toplevel"]) - .string(); - } - else { - git_relative_path = ""; - } - - ctx["element"]["source"]["path"] = util::path_to_url(git_relative_path); - ctx["element"]["source"]["full_path"] = file.string(); - ctx["element"]["source"]["name"] = file.filename().string(); - ctx["element"]["source"]["line"] = e.line(); - } - - const auto maybe_comment = e.comment(); - if (maybe_comment) { - ctx["element"]["comment"] = maybe_comment.value(); - } - - return ctx; -} - template void generator::generate(std::ostream &ostr) const { @@ -259,7 +184,7 @@ void generator::generate(std::ostream &ostr) const "Diagram configuration resulted in empty diagram."}; } - update_context(); + generators::generator::update_context(); generate_title(ostr); @@ -278,55 +203,66 @@ template template void generator::generate_link(std::ostream &ostr, const E &e) const { - const auto &config = generators::generator::config(); - - if (e.file().empty()) + if (e.file().empty() && e.file_relative().empty()) return; - if (config.generate_links().link.empty() && - config.generate_links().tooltip.empty()) + auto maybe_link_pattern = generators::generator::get_link_pattern(e); + + if (!maybe_link_pattern) return; + const auto &[link_prefix, link_pattern] = *maybe_link_pattern; + ostr << indent(1) << "click " << e.alias() << " href \""; try { + auto ec = generators::generator::element_context(e); + common::generators::make_context_source_relative(ec, link_prefix); std::string link{}; - if (!config.generate_links().link.empty()) { - link = env().render(std::string_view{config.generate_links().link}, - element_context(e)); + if (!link_pattern.empty()) { + link = generators::generator::env().render( + std::string_view{link_pattern}, ec); } if (link.empty()) link = " "; ostr << link; } catch (const inja::json::parse_error &e) { - LOG_ERROR( - "Failed to parse Jinja template: {}", config.generate_links().link); + LOG_ERROR("Failed to parse Jinja template: {}", link_pattern); ostr << " "; } catch (const inja::json::exception &e) { LOG_ERROR("Failed to render comment directive: \n{}\n due to: {}", - config.generate_links().link, e.what()); + link_pattern, e.what()); ostr << " "; } ostr << "\""; - if (!config.generate_links().tooltip.empty()) { + auto maybe_tooltip_pattern = + generators::generator::get_tooltip_pattern(e); + + if (!maybe_tooltip_pattern) + return; + + const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; + + if (!tooltip_pattern.empty()) { ostr << " \""; try { - auto tooltip_text = - env().render(std::string_view{config.generate_links().tooltip}, - element_context(e)); + auto ec = generators::generator::element_context(e); + common::generators::make_context_source_relative( + ec, tooltip_prefix); + auto tooltip_text = generators::generator::env().render( + std::string_view{tooltip_pattern}, ec); util::replace_all(tooltip_text, "\"", "„"); ostr << tooltip_text; } catch (const inja::json::parse_error &e) { - LOG_ERROR("Failed to parse Jinja template: {}", - config.generate_links().link); + LOG_ERROR("Failed to parse Jinja template: {}", tooltip_pattern); ostr << " "; } catch (const inja::json::exception &e) { LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", - config.generate_links().link, e.what()); + tooltip_pattern, e.what()); ostr << " "; } @@ -348,7 +284,8 @@ void generator::generate_mermaid_directives( for (const auto &d : directives) { try { // Render the directive with template engine first - std::string directive{env().render(std::string_view{d}, context())}; + std::string directive{generators::generator::env().render( + std::string_view{d}, generators::generator::context())}; // Now search for alias `@A()` directives in the text // (this is deprecated) @@ -453,127 +390,4 @@ std::ostream &operator<<( g.generate(os); return os; } - -template void generator::init_context() -{ - const auto &config = generators::generator::config(); - - if (config.git) { - m_context["git"]["branch"] = config.git().branch; - m_context["git"]["revision"] = config.git().revision; - m_context["git"]["commit"] = config.git().commit; - m_context["git"]["toplevel"] = config.git().toplevel; - } -} - -template void generator::update_context() const -{ - m_context["diagram"] = generators::generator::model().context(); -} - -template void generator::init_env() -{ - const auto &model = generators::generator::model(); - const auto &config = generators::generator::config(); - - // - // Add basic string functions to inja environment - // - - // Check if string is empty - m_env.add_callback("empty", 1, [](inja::Arguments &args) { - return args.at(0)->get().empty(); - }); - - // Remove spaces from the left of a string - m_env.add_callback("ltrim", 1, [](inja::Arguments &args) { - return util::ltrim(args.at(0)->get()); - }); - - // Remove trailing spaces from a string - m_env.add_callback("rtrim", 1, [](inja::Arguments &args) { - return util::rtrim(args.at(0)->get()); - }); - - // Remove spaces before and after a string - m_env.add_callback("trim", 1, [](inja::Arguments &args) { - return util::trim(args.at(0)->get()); - }); - - // Make a string shorted with a limit to - m_env.add_callback("abbrv", 2, [](inja::Arguments &args) { - return util::abbreviate( - args.at(0)->get(), args.at(1)->get()); - }); - - m_env.add_callback("replace", 3, [](inja::Arguments &args) { - std::string result = args[0]->get(); - std::regex pattern(args[1]->get()); - return std::regex_replace(result, pattern, args[2]->get()); - }); - - m_env.add_callback("split", 2, [](inja::Arguments &args) { - return util::split( - args[0]->get(), args[1]->get()); - }); - - // - // Add MermaidJS specific functions - // - - // Return the entire element JSON context based on element name - // e.g.: - // {{ element("clanguml::t00050::A").comment }} - // - m_env.add_callback("element", 1, [&model, &config](inja::Arguments &args) { - inja::json res{}; - auto element_opt = model.get_with_namespace( - args[0]->get(), config.using_namespace()); - - if (element_opt.has_value()) - res = element_opt.value().context(); - - return res; - }); - - // Convert C++ entity to MermaidJS alias, e.g. - // "note left of {{ alias("A") }}: This is a note" - // Shortcut to: - // {{ element("A").alias }} - // - m_env.add_callback("alias", 1, [&model, &config](inja::Arguments &args) { - auto element_opt = model.get_with_namespace( - args[0]->get(), config.using_namespace()); - - if (!element_opt.has_value()) - throw clanguml::error::uml_alias_missing( - args[0]->get()); - - return element_opt.value().alias(); - }); - - // Get elements' comment: - // "note left of {{ alias("A") }}: {{ comment("A") }}" - // Shortcut to: - // {{ element("A").comment }} - // - m_env.add_callback("comment", 1, [&model, &config](inja::Arguments &args) { - inja::json res{}; - auto element_opt = model.get_with_namespace( - args[0]->get(), config.using_namespace()); - - if (!element_opt.has_value()) - throw clanguml::error::uml_alias_missing( - args[0]->get()); - - auto comment = element_opt.value().comment(); - - if (comment.has_value()) { - assert(comment.value().is_object()); - res = comment.value(); - } - - return res; - }); -} } // namespace clanguml::common::generators::mermaid \ No newline at end of file diff --git a/src/common/generators/plantuml/generator.h b/src/common/generators/plantuml/generator.h index 9adc60bc..d5d845ed 100644 --- a/src/common/generators/plantuml/generator.h +++ b/src/common/generators/plantuml/generator.h @@ -21,7 +21,6 @@ #include "common/model/filters/diagram_filter.h" #include "common/model/relationship.h" #include "config/config.h" -#include "util/error.h" #include "util/util.h" #include "version.h" @@ -63,8 +62,6 @@ public: : clanguml::common::generators::generator{ config, model} { - init_context(); - init_env(); } ~generator() override = default; @@ -177,21 +174,6 @@ public: */ void print_debug( const common::model::source_location &e, std::ostream &ostr) const; - /** - * @brief Update diagram Jinja context - * - * This method updates the diagram context with models properties - * which can be used to render Jinja templates in the diagram (e.g. - * in notes or links) - */ - void update_context() const; - -protected: - const inja::json &context() const; - - inja::Environment &env() const; - - template inja::json element_context(const E &e) const; private: void generate_row_column_hints(std::ostream &ostr, @@ -200,74 +182,17 @@ private: void generate_position_hints(std::ostream &ostr, const std::string &entity_name, const config::layout_hint &hint) const; - void init_context(); - - void init_env(); - protected: mutable std::set m_generated_aliases; - mutable inja::json m_context; - mutable inja::Environment m_env; }; -template -const inja::json &generator::context() const -{ - return m_context; -} - -template -inja::Environment &generator::env() const -{ - return m_env; -} - -template -template -inja::json generator::element_context(const E &e) const -{ - auto ctx = context(); - - ctx["element"] = e.context(); - - if (!e.file().empty()) { - std::filesystem::path file{e.file()}; - std::string git_relative_path = file.string(); - if (!e.file_relative().empty()) { -#if _MSC_VER - if (file.is_absolute() && ctx.contains("git")) -#else - if (file.is_absolute() && ctx.template contains("git")) -#endif - git_relative_path = - std::filesystem::relative(file, ctx["git"]["toplevel"]) - .string(); - } - else { - git_relative_path = ""; - } - - ctx["element"]["source"]["path"] = util::path_to_url(git_relative_path); - ctx["element"]["source"]["full_path"] = file.string(); - ctx["element"]["source"]["name"] = file.filename().string(); - ctx["element"]["source"]["line"] = e.line(); - } - - const auto &maybe_comment = e.comment(); - if (maybe_comment) { - ctx["element"]["comment"] = maybe_comment.value(); - } - - return ctx; -} - template void generator::generate(std::ostream &ostr) const { const auto &config = generators::generator::config(); const auto &model = generators::generator::model(); - update_context(); + generators::generator::update_context(); if (!config.allow_empty_diagrams() && model.is_empty() && config.puml().before.empty() && config.puml().after.empty()) { @@ -413,7 +338,8 @@ void generator::generate_plantuml_directives( for (const auto &d : directives) { try { // Render the directive with template engine first - std::string directive{env().render(std::string_view{d}, context())}; + std::string directive{generators::generator::env().render( + std::string_view{d}, generators::generator::context())}; // Now search for alias `@A()` directives in the text // (this is deprecated) @@ -519,42 +445,56 @@ template template void generator::generate_link(std::ostream &ostr, const E &e) const { - const auto &config = generators::generator::config(); - - if (e.file_relative().empty()) + if (e.file().empty() && e.file_relative().empty()) return; + auto maybe_link_pattern = generators::generator::get_link_pattern(e); + + if (!maybe_link_pattern) + return; + + const auto &[link_prefix, link_pattern] = *maybe_link_pattern; + ostr << " [["; try { - if (!config.generate_links().link.empty()) { - ostr << env().render(std::string_view{config.generate_links().link}, - element_context(e)); + if (!link_pattern.empty()) { + auto ec = generators::generator::element_context(e); + common::generators::make_context_source_relative(ec, link_prefix); + ostr << generators::generator::env().render( + std::string_view{link_pattern}, ec); } } catch (const inja::json::parse_error &e) { - LOG_ERROR( - "Failed to parse Jinja template: {}", config.generate_links().link); + LOG_ERROR("Failed to parse Jinja template: {}", link_pattern); } catch (const inja::json::exception &e) { LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", - config.generate_links().link, e.what()); + link_pattern, e.what()); } + auto maybe_tooltip_pattern = + generators::generator::get_tooltip_pattern(e); + + if (!maybe_tooltip_pattern) + return; + + const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; + ostr << "{"; try { - if (!config.generate_links().tooltip.empty()) { - ostr << env().render( - std::string_view{config.generate_links().tooltip}, - element_context(e)); + auto ec = generators::generator::element_context(e); + common::generators::make_context_source_relative(ec, tooltip_prefix); + if (!tooltip_pattern.empty()) { + ostr << generators::generator::env().render( + std::string_view{tooltip_pattern}, ec); } } catch (const inja::json::parse_error &e) { - LOG_ERROR( - "Failed to parse Jinja template: {}", config.generate_links().link); + LOG_ERROR("Failed to parse Jinja template: {}", tooltip_pattern); } catch (const inja::json::exception &e) { LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", - config.generate_links().link, e.what()); + tooltip_pattern, e.what()); } ostr << "}"; @@ -579,126 +519,4 @@ std::ostream &operator<<( return os; } -template void generator::init_context() -{ - const auto &config = generators::generator::config(); - - if (config.git) { - m_context["git"]["branch"] = config.git().branch; - m_context["git"]["revision"] = config.git().revision; - m_context["git"]["commit"] = config.git().commit; - m_context["git"]["toplevel"] = config.git().toplevel; - } -} - -template void generator::update_context() const -{ - m_context["diagram"] = generators::generator::model().context(); -} - -template void generator::init_env() -{ - const auto &model = generators::generator::model(); - const auto &config = generators::generator::config(); - - // - // Add basic string functions to inja environment - // - - // Check if string is empty - m_env.add_callback("empty", 1, [](inja::Arguments &args) { - return args.at(0)->get().empty(); - }); - - // Remove spaces from the left of a string - m_env.add_callback("ltrim", 1, [](inja::Arguments &args) { - return util::ltrim(args.at(0)->get()); - }); - - // Remove trailing spaces from a string - m_env.add_callback("rtrim", 1, [](inja::Arguments &args) { - return util::rtrim(args.at(0)->get()); - }); - - // Remove spaces before and after a string - m_env.add_callback("trim", 1, [](inja::Arguments &args) { - return util::trim(args.at(0)->get()); - }); - - // Make a string shorted with a limit to - m_env.add_callback("abbrv", 2, [](inja::Arguments &args) { - return util::abbreviate( - args.at(0)->get(), args.at(1)->get()); - }); - - m_env.add_callback("replace", 3, [](inja::Arguments &args) { - std::string result = args[0]->get(); - std::regex pattern(args[1]->get()); - return std::regex_replace(result, pattern, args[2]->get()); - }); - - m_env.add_callback("split", 2, [](inja::Arguments &args) { - return util::split( - args[0]->get(), args[1]->get()); - }); - - // - // Add PlantUML specific functions - // - - // Return the entire element JSON context based on element name - // e.g.: - // {{ element("clanguml::t00050::A").comment }} - // - m_env.add_callback("element", 1, [&model, &config](inja::Arguments &args) { - inja::json res{}; - auto element_opt = model.get_with_namespace( - args[0]->get(), config.using_namespace()); - - if (element_opt.has_value()) - res = element_opt.value().context(); - - return res; - }); - - // Convert C++ entity to PlantUML alias, e.g. - // "note left of {{ alias("A") }}: This is a note" - // Shortcut to: - // {{ element("A").alias }} - // - m_env.add_callback("alias", 1, [&model, &config](inja::Arguments &args) { - auto element_opt = model.get_with_namespace( - args[0]->get(), config.using_namespace()); - - if (!element_opt.has_value()) - throw clanguml::error::uml_alias_missing( - args[0]->get()); - - return element_opt.value().alias(); - }); - - // Get elements' comment: - // "note left of {{ alias("A") }}: {{ comment("A") }}" - // Shortcut to: - // {{ element("A").comment }} - // - m_env.add_callback("comment", 1, [&model, &config](inja::Arguments &args) { - inja::json res{}; - auto element_opt = model.get_with_namespace( - args[0]->get(), config.using_namespace()); - - if (!element_opt.has_value()) - throw clanguml::error::uml_alias_missing( - args[0]->get()); - - auto comment = element_opt.value().comment(); - - if (comment.has_value()) { - assert(comment.value().is_object()); - res = comment.value(); - } - - return res; - }); -} } // namespace clanguml::common::generators::plantuml \ No newline at end of file diff --git a/src/config/config.h b/src/config/config.h index d9b40de6..4c3b1126 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -455,8 +455,44 @@ struct layout_hint { using layout_hints = std::map>; struct generate_links_config { - std::string link; - std::string tooltip; + std::map link; + std::map tooltip; + + std::optional> get_link_pattern( + const std::string &path) const + { + if (link.empty()) + return {}; + + if ((path.empty() || path == ".") && link.count(".") > 0) { + return {{".", link.at(".")}}; + } + + for (const auto &[key, pattern] : link) { + if (util::starts_with(path, key)) + return {{key, pattern}}; + } + + return {}; + } + + std::optional> get_tooltip_pattern( + const std::string &path) const + { + if (tooltip.empty()) + return {}; + + if ((path.empty() || path == ".") && tooltip.count(".") > 0) { + return {{".", tooltip.at(".")}}; + } + + for (const auto &[key, pattern] : tooltip) { + if (util::starts_with(path, key)) + return {{key, pattern}}; + } + + return {}; + } }; struct git_config { diff --git a/src/config/schema.h b/src/config/schema.h index ebaa3d47..7a3bfdbf 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -37,8 +37,8 @@ types: - abbreviated - none generate_links_t: - link: string - tooltip: string + link: !optional [string, map_t] + tooltip: !optional [string, map_t] git_t: branch: string revision: [string, int] diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index f4994b2a..a9b5fe9f 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -616,11 +616,19 @@ template <> struct convert { template <> struct convert { static bool decode(const Node &node, generate_links_config &rhs) { - if (node["link"]) - rhs.link = node["link"].as(); + if (node["link"]) { + if (node["link"].IsMap()) + rhs.link = node["link"].as(); + else + rhs.link.emplace(".", node["link"].as()); + } - if (node["tooltip"]) - rhs.tooltip = node["tooltip"].as(); + if (node["tooltip"]) { + if (node["tooltip"].IsMap()) + rhs.tooltip = node["tooltip"].as(); + else + rhs.tooltip.emplace(".", node["tooltip"].as()); + } return true; } diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 1994881c..d45ae925 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -30,11 +30,14 @@ void inject_diagram_options(std::shared_ptr diagram) { // Inject links config to all test cases - clanguml::config::generate_links_config links_config{ - R"(https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }})", - R"({% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"}; + clanguml::config::generate_links_config links_config; - diagram->generate_links.set(links_config); + links_config.link.emplace(".", + R"(https://github.com/bkryza/clang-uml/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }})"); + links_config.tooltip.emplace(".", + R"({% if existsIn(element, "comment") and existsIn(element.comment, "brief") %}{{ abbrv(trim(replace(element.comment.brief.0, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %})"); + + diagram->generate_links.set(std::move(links_config)); } std::pair()); } catch (doctest::TestFailureException &e) { - std::cout << "-----------------------------------------------------" + std::cout << "---------------------------------------------" + "--------" "--------------------------\n"; std::cout << "Test case failed for diagram type " << T::diagram_type_name << ": " diff --git a/tests/test_config.cc b/tests/test_config.cc index 9fd3195f..1c0d073d 100644 --- a/tests/test_config.cc +++ b/tests/test_config.cc @@ -43,10 +43,10 @@ TEST_CASE("Test config simple") CHECK(diagram.generate_packages() == true); CHECK(diagram.generate_template_argument_dependencies() == false); CHECK(diagram.generate_links == true); - CHECK(diagram.generate_links().link == + CHECK(diagram.generate_links().link.at(".") == "https://github.com/bkryza/clang-uml/blob/{{ git.branch }}/{{ " "element.source.file }}#L{{ element.source.line }}"); - CHECK(diagram.generate_links().tooltip == "{{ element.comment }}"); + CHECK(diagram.generate_links().tooltip.at(".") == "{{ element.comment }}"); CHECK( diagram.comment_parser() == clanguml::config::comment_parser_t::clang);