Enabled multiple relative link patterns in generate_links option (#297)
This commit is contained in:
@@ -85,25 +85,14 @@ public:
|
|||||||
template <typename E> inja::json element_context(const E &e) const;
|
template <typename E> inja::json element_context(const E &e) const;
|
||||||
|
|
||||||
std::optional<std::pair<std::string, std::string>> get_link_pattern(
|
std::optional<std::pair<std::string, std::string>> get_link_pattern(
|
||||||
const common::model::source_location &sl) const
|
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<std::pair<std::string, std::string>> get_tooltip_pattern(
|
std::optional<std::pair<std::string, std::string>> get_tooltip_pattern(
|
||||||
const common::model::source_location &sl) const
|
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(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize diagram Jinja context
|
||||||
|
*/
|
||||||
void init_context();
|
void init_context();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,4 +307,28 @@ inja::json generator<C, D>::element_context(const E &e) const
|
|||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename C, typename D>
|
||||||
|
std::optional<std::pair<std::string, std::string>>
|
||||||
|
generator<C, D>::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 <typename C, typename D>
|
||||||
|
std::optional<std::pair<std::string, std::string>>
|
||||||
|
generator<C, D>::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
|
} // namespace clanguml::common::generators
|
||||||
@@ -240,9 +240,7 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
|||||||
auto maybe_tooltip_pattern =
|
auto maybe_tooltip_pattern =
|
||||||
generators::generator<C, D>::get_tooltip_pattern(e);
|
generators::generator<C, D>::get_tooltip_pattern(e);
|
||||||
|
|
||||||
if (!maybe_tooltip_pattern)
|
if (maybe_tooltip_pattern) {
|
||||||
return;
|
|
||||||
|
|
||||||
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
|
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
|
||||||
|
|
||||||
if (!tooltip_pattern.empty()) {
|
if (!tooltip_pattern.empty()) {
|
||||||
@@ -257,17 +255,20 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
|||||||
ostr << tooltip_text;
|
ostr << tooltip_text;
|
||||||
}
|
}
|
||||||
catch (const inja::json::parse_error &e) {
|
catch (const inja::json::parse_error &e) {
|
||||||
LOG_ERROR("Failed to parse Jinja template: {}", tooltip_pattern);
|
LOG_ERROR(
|
||||||
|
"Failed to parse Jinja template: {}", tooltip_pattern);
|
||||||
ostr << " ";
|
ostr << " ";
|
||||||
}
|
}
|
||||||
catch (const inja::json::exception &e) {
|
catch (const inja::json::exception &e) {
|
||||||
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
|
LOG_ERROR(
|
||||||
|
"Failed to render PlantUML directive: \n{}\n due to: {}",
|
||||||
tooltip_pattern, e.what());
|
tooltip_pattern, e.what());
|
||||||
ostr << " ";
|
ostr << " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
ostr << "\"";
|
ostr << "\"";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ostr << "\n";
|
ostr << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -450,8 +450,9 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
|||||||
|
|
||||||
auto maybe_link_pattern = generators::generator<C, D>::get_link_pattern(e);
|
auto maybe_link_pattern = generators::generator<C, D>::get_link_pattern(e);
|
||||||
|
|
||||||
if (!maybe_link_pattern)
|
if (!maybe_link_pattern) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto &[link_prefix, link_pattern] = *maybe_link_pattern;
|
const auto &[link_prefix, link_pattern] = *maybe_link_pattern;
|
||||||
|
|
||||||
@@ -467,7 +468,7 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
|||||||
catch (const inja::json::parse_error &e) {
|
catch (const inja::json::parse_error &e) {
|
||||||
LOG_ERROR("Failed to parse Jinja template: {}", link_pattern);
|
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: {}",
|
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
|
||||||
link_pattern, e.what());
|
link_pattern, e.what());
|
||||||
}
|
}
|
||||||
@@ -475,15 +476,14 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
|||||||
auto maybe_tooltip_pattern =
|
auto maybe_tooltip_pattern =
|
||||||
generators::generator<C, D>::get_tooltip_pattern(e);
|
generators::generator<C, D>::get_tooltip_pattern(e);
|
||||||
|
|
||||||
if (!maybe_tooltip_pattern)
|
if (maybe_tooltip_pattern) {
|
||||||
return;
|
|
||||||
|
|
||||||
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
|
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
|
||||||
|
|
||||||
ostr << "{";
|
ostr << "{";
|
||||||
try {
|
try {
|
||||||
auto ec = generators::generator<C, D>::element_context(e);
|
auto ec = generators::generator<C, D>::element_context(e);
|
||||||
common::generators::make_context_source_relative(ec, tooltip_prefix);
|
common::generators::make_context_source_relative(
|
||||||
|
ec, tooltip_prefix);
|
||||||
if (!tooltip_pattern.empty()) {
|
if (!tooltip_pattern.empty()) {
|
||||||
ostr << generators::generator<C, D>::env().render(
|
ostr << generators::generator<C, D>::env().render(
|
||||||
std::string_view{tooltip_pattern}, ec);
|
std::string_view{tooltip_pattern}, ec);
|
||||||
@@ -492,12 +492,12 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
|||||||
catch (const inja::json::parse_error &e) {
|
catch (const inja::json::parse_error &e) {
|
||||||
LOG_ERROR("Failed to parse Jinja template: {}", tooltip_pattern);
|
LOG_ERROR("Failed to parse Jinja template: {}", tooltip_pattern);
|
||||||
}
|
}
|
||||||
catch (const inja::json::exception &e) {
|
catch (const std::exception &e) {
|
||||||
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
|
LOG_ERROR("Failed to render PlantUML directive: \n{}\n due to: {}",
|
||||||
tooltip_pattern, e.what());
|
tooltip_pattern, e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
ostr << "}";
|
ostr << "}";
|
||||||
|
}
|
||||||
ostr << "]]";
|
ostr << "]]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -461,37 +461,13 @@ struct generate_links_config {
|
|||||||
std::optional<std::pair<std::string, std::string>> get_link_pattern(
|
std::optional<std::pair<std::string, std::string>> get_link_pattern(
|
||||||
const std::string &path) const
|
const std::string &path) const
|
||||||
{
|
{
|
||||||
if (link.empty())
|
return util::find_entry_by_path_prefix(link, path);
|
||||||
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<std::pair<std::string, std::string>> get_tooltip_pattern(
|
std::optional<std::pair<std::string, std::string>> get_tooltip_pattern(
|
||||||
const std::string &path) const
|
const std::string &path) const
|
||||||
{
|
{
|
||||||
if (tooltip.empty())
|
return util::find_entry_by_path_prefix(tooltip, path);
|
||||||
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 {};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -496,4 +496,75 @@ std::string format_message_comment(const std::string &comment, unsigned width)
|
|||||||
return result;
|
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<std::pair<std::string, std::string>> find_entry_by_path_prefix(
|
||||||
|
const std::map<std::string, std::string> &m, const std::string &path)
|
||||||
|
{
|
||||||
|
if (m.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Extract keys and sort them by length in descending order
|
||||||
|
std::vector<std::string> 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
|
} // namespace clanguml::util
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -460,4 +462,9 @@ bool is_relative_to(
|
|||||||
std::string format_message_comment(
|
std::string format_message_comment(
|
||||||
const std::string &c, unsigned width = kDefaultMessageCommentWidth);
|
const std::string &c, unsigned width = kDefaultMessageCommentWidth);
|
||||||
|
|
||||||
|
bool is_subpath(
|
||||||
|
const std::filesystem::path &path, const std::filesystem::path &prefix);
|
||||||
|
|
||||||
|
std::optional<std::pair<std::string, std::string>> find_entry_by_path_prefix(
|
||||||
|
const std::map<std::string, std::string> &m, const std::string &prefix);
|
||||||
} // namespace clanguml::util
|
} // namespace clanguml::util
|
||||||
@@ -475,3 +475,63 @@ TEST_CASE("Test parse_source_location")
|
|||||||
|
|
||||||
result = false, file = "", line = 0, column = 0;
|
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<std::string, std::string> 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");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user