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;
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>> 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<C, D>::element_context(const E &e) const
|
||||
|
||||
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
|
||||
@@ -240,9 +240,7 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
||||
auto maybe_tooltip_pattern =
|
||||
generators::generator<C, D>::get_tooltip_pattern(e);
|
||||
|
||||
if (!maybe_tooltip_pattern)
|
||||
return;
|
||||
|
||||
if (maybe_tooltip_pattern) {
|
||||
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
|
||||
|
||||
if (!tooltip_pattern.empty()) {
|
||||
@@ -257,17 +255,20 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
||||
ostr << tooltip_text;
|
||||
}
|
||||
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 << " ";
|
||||
}
|
||||
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());
|
||||
ostr << " ";
|
||||
}
|
||||
|
||||
ostr << "\"";
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
if (!maybe_link_pattern)
|
||||
if (!maybe_link_pattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
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,15 +476,14 @@ void generator<C, D>::generate_link(std::ostream &ostr, const E &e) const
|
||||
auto maybe_tooltip_pattern =
|
||||
generators::generator<C, D>::get_tooltip_pattern(e);
|
||||
|
||||
if (!maybe_tooltip_pattern)
|
||||
return;
|
||||
|
||||
if (maybe_tooltip_pattern) {
|
||||
const auto &[tooltip_prefix, tooltip_pattern] = *maybe_tooltip_pattern;
|
||||
|
||||
ostr << "{";
|
||||
try {
|
||||
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()) {
|
||||
ostr << generators::generator<C, D>::env().render(
|
||||
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) {
|
||||
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: {}",
|
||||
tooltip_pattern, e.what());
|
||||
}
|
||||
|
||||
ostr << "}";
|
||||
}
|
||||
ostr << "]]";
|
||||
}
|
||||
|
||||
|
||||
@@ -461,37 +461,13 @@ struct generate_links_config {
|
||||
std::optional<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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<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
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
@@ -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<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
|
||||
@@ -475,3 +475,63 @@ TEST_CASE("Test parse_source_location")
|
||||
|
||||
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