From f97d42083b361a3645a153f522c47c8e33ed62aa Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 28 Jul 2024 18:33:14 +0200 Subject: [PATCH] Enabled multiple relative link patterns in generate_links option (#297) --- src/common/generators/generator.h | 45 +++++++++----- src/common/generators/mermaid/generator.h | 49 +++++++-------- src/common/generators/plantuml/generator.h | 44 +++++++------- src/config/config.h | 28 +-------- src/util/util.cc | 71 ++++++++++++++++++++++ src/util/util.h | 7 +++ tests/test_util.cc | 60 ++++++++++++++++++ 7 files changed, 216 insertions(+), 88 deletions(-) diff --git a/src/common/generators/generator.h b/src/common/generators/generator.h index 3715c03c..708ca50a 100644 --- a/src/common/generators/generator.h +++ b/src/common/generators/generator.h @@ -85,25 +85,14 @@ public: 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("."); - } + const common::model::source_location &sl) const; 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("."); - } + const common::model::source_location &sl) const; + /** + * @brief Initialize diagram Jinja context + */ void init_context(); /** @@ -318,4 +307,28 @@ inja::json generator::element_context(const E &e) const return ctx; } + +template +std::optional> +generator::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(sl.file_relative()); +} + +template +std::optional> +generator::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(sl.file_relative()); +} } // namespace clanguml::common::generators \ No newline at end of file diff --git a/src/common/generators/mermaid/generator.h b/src/common/generators/mermaid/generator.h index 6a24e2f1..98bfe937 100644 --- a/src/common/generators/mermaid/generator.h +++ b/src/common/generators/mermaid/generator.h @@ -240,33 +240,34 @@ void generator::generate_link(std::ostream &ostr, const E &e) const auto maybe_tooltip_pattern = generators::generator::get_tooltip_pattern(e); - if (!maybe_tooltip_pattern) - return; + if (maybe_tooltip_pattern) { + const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; - const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; + if (!tooltip_pattern.empty()) { + ostr << " \""; + try { + 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: {}", tooltip_pattern); + ostr << " "; + } + catch (const inja::json::exception &e) { + LOG_ERROR( + "Failed to render PlantUML directive: \n{}\n due to: {}", + tooltip_pattern, e.what()); + ostr << " "; + } - if (!tooltip_pattern.empty()) { - ostr << " \""; - try { - 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; + ostr << "\""; } - catch (const inja::json::parse_error &e) { - 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: {}", - tooltip_pattern, e.what()); - ostr << " "; - } - - ostr << "\""; } ostr << "\n"; } diff --git a/src/common/generators/plantuml/generator.h b/src/common/generators/plantuml/generator.h index d5d845ed..8717d46a 100644 --- a/src/common/generators/plantuml/generator.h +++ b/src/common/generators/plantuml/generator.h @@ -450,8 +450,9 @@ void generator::generate_link(std::ostream &ostr, const E &e) const auto maybe_link_pattern = generators::generator::get_link_pattern(e); - if (!maybe_link_pattern) + if (!maybe_link_pattern) { return; + } const auto &[link_prefix, link_pattern] = *maybe_link_pattern; @@ -467,7 +468,7 @@ void generator::generate_link(std::ostream &ostr, const E &e) const catch (const inja::json::parse_error &e) { LOG_ERROR("Failed to parse Jinja template: {}", link_pattern); } - catch (const inja::json::exception &e) { + catch (const std::exception &e) { LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", link_pattern, e.what()); } @@ -475,29 +476,28 @@ void generator::generate_link(std::ostream &ostr, const E &e) const auto maybe_tooltip_pattern = generators::generator::get_tooltip_pattern(e); - if (!maybe_tooltip_pattern) - return; + if (maybe_tooltip_pattern) { + const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; - const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern; - - ostr << "{"; - try { - 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); + ostr << "{"; + try { + 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: {}", tooltip_pattern); + } + catch (const std::exception &e) { + LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}", + tooltip_pattern, e.what()); + } + ostr << "}"; } - catch (const inja::json::parse_error &e) { - 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: {}", - tooltip_pattern, e.what()); - } - - ostr << "}"; ostr << "]]"; } diff --git a/src/config/config.h b/src/config/config.h index 4c3b1126..3b95e561 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -461,37 +461,13 @@ struct generate_links_config { 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 {}; + return util::find_entry_by_path_prefix(link, path); } 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 {}; + return util::find_entry_by_path_prefix(tooltip, path); } }; diff --git a/src/util/util.cc b/src/util/util.cc index a06cf70d..7cc92972 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -496,4 +496,75 @@ std::string format_message_comment(const std::string &comment, unsigned width) return result; } +std::filesystem::path normalize_relative_path(const std::filesystem::path &path) +{ + if (path.is_absolute()) + return path; + + std::filesystem::path result; + + for (const auto &part : path) { + if (part == ".") { + continue; + } + result /= part; + } + + return result; +} + +bool is_subpath( + const std::filesystem::path &path, const std::filesystem::path &base) +{ + if (path.empty()) + return false; + + auto normalized_path = normalize_relative_path(path); + auto normalized_base = normalize_relative_path(base); + + if (normalized_path == normalized_base) + return true; + + auto rel = std::filesystem::relative(normalized_path, normalized_base); + + std::string rel_str = rel.string(); + return !rel_str.empty() && rel.native()[0] != '.'; +} + +std::optional> find_entry_by_path_prefix( + const std::map &m, const std::string &path) +{ + if (m.empty()) + return {}; + + // Extract keys and sort them by length in descending order + std::vector keys; + keys.reserve(m.size()); + for (const auto &[key, pattern] : m) { + keys.push_back(key); + } + + std::sort(keys.begin(), keys.end(), + [](const std::string &a, const std::string &b) { + return a.size() > b.size(); + }); + + std::filesystem::path file_path{path}; + + for (const auto &key : keys) { + const auto &pattern = m.at(key); + std::filesystem::path key_path{key}; + + if (is_subpath(file_path, key_path)) { + return {{key, pattern}}; + } + } + + if ((path.empty() || file_path.is_relative() || path == ".") && + m.count(".") > 0) { + return {{".", m.at(".")}}; + } + + return {}; +} } // namespace clanguml::util diff --git a/src/util/util.h b/src/util/util.h index 1db0af1d..45c6a986 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -460,4 +462,9 @@ bool is_relative_to( std::string format_message_comment( const std::string &c, unsigned width = kDefaultMessageCommentWidth); +bool is_subpath( + const std::filesystem::path &path, const std::filesystem::path &prefix); + +std::optional> find_entry_by_path_prefix( + const std::map &m, const std::string &prefix); } // namespace clanguml::util \ No newline at end of file diff --git a/tests/test_util.cc b/tests/test_util.cc index 4caa265b..939fc64b 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -474,4 +474,64 @@ TEST_CASE("Test parse_source_location") CHECK(column == 456); result = false, file = "", line = 0, column = 0; +} + +TEST_CASE("Test is_subpath") +{ + using clanguml::util::is_subpath; + + CHECK(is_subpath("./include/f.h", ".")); + CHECK(is_subpath("include/f.h", ".")); + CHECK(is_subpath("./include/f.h", "./")); + CHECK(is_subpath("./include/f.h", "./include")); + CHECK(is_subpath("./include/f.h", "include")); + CHECK(is_subpath("include/f.h", "./include")); + CHECK(is_subpath("include/f.h", "include")); + + CHECK(is_subpath("include/f.h", "include/f.h")); + CHECK(is_subpath("./include/f.h", "include/f.h")); + CHECK(is_subpath("include/f.h", "./include/f.h")); + CHECK(is_subpath("./include/f.h", "include/f.h")); + +#if !defined(_MSC_VER) + CHECK(is_subpath("/usr/local/include/f.h", "/usr/local")); + CHECK_FALSE(is_subpath("/usr/local/include/f.h", "/usr/local/lib")); + CHECK_FALSE(is_subpath("/usr/include/f.h", ".")); +#else + CHECK(is_subpath("E:\\test\\src\\main.cpp", "E:\\test")); + CHECK_FALSE(is_subpath("E:\\test\\src\\main.cpp", "C:\\test")); +#endif +} + +TEST_CASE("Test find_entry_by_path_prefix") +{ + using clanguml::util::find_entry_by_path_prefix; + + std::map m; + m.emplace(".", "default"); + m.emplace("./src", "internal_sources"); + m.emplace("./src/thirdparty/", "thirdparty_sources"); + m.emplace("/usr/include/boost", "boost_sources"); + m.emplace("../my_other_project/src", "my_other_project_sources"); + + auto kv = find_entry_by_path_prefix(m, "/tmp"); + CHECK_FALSE(kv.has_value()); + + kv = find_entry_by_path_prefix(m, "./src/main.cc"); + CHECK(kv.value().second == "internal_sources"); + + kv = find_entry_by_path_prefix(m, "src/main.cc"); + CHECK(kv.value().second == "internal_sources"); + + kv = find_entry_by_path_prefix(m, "src/thirdparty/main.cc"); + CHECK(kv.value().second == "thirdparty_sources"); + + kv = find_entry_by_path_prefix(m, "include/f.h"); + CHECK(kv.value().second == "default"); + + kv = find_entry_by_path_prefix(m, "./include/f.h"); + CHECK(kv.value().second == "default"); + + kv = find_entry_by_path_prefix(m, "../my_other_project/src/lib.cc"); + CHECK(kv.value().second == "my_other_project_sources"); } \ No newline at end of file