20#include <spdlog/spdlog.h>
23#if __has_include(<sys/utsname.h>)
24#include <sys/utsname.h>
34 explicit pipe_t(
const std::string &command,
int &result)
37#if defined(__linux) || defined(__unix) || defined(__APPLE__)
38 pipe_{popen(fmt::format(
"{} 2>&1", command).c_str(),
"r")}
40 pipe_{_popen(command.c_str(),
"r")}
45 ~pipe_t() { reset(); }
47 operator bool()
const {
return pipe_ !=
nullptr; }
49 FILE *get()
const {
return pipe_; }
56#if defined(__linux) || defined(__unix) || defined(__APPLE__)
72 constexpr size_t kBufferSize{1024};
73 std::array<char, kBufferSize> buffer{};
75 int result{EXIT_FAILURE};
77 pipe_t pipe{command, result};
80 throw std::runtime_error(
"popen() failed!");
83 while (fgets(buffer.data(), buffer.size(), pipe.get()) !=
nullptr) {
84 output += buffer.data();
89 if (result != EXIT_SUCCESS) {
90 throw std::runtime_error(
91 fmt::format(
"External command '{}' failed: {}", command, output));
99 constexpr size_t kBufferSize{1024};
100 std::array<char, kBufferSize> buffer{};
101 int result{EXIT_FAILURE};
103 pipe_t pipe{command, result};
106 throw std::runtime_error(
"popen() failed!");
109 while (fgets(buffer.data(), buffer.size(), pipe.get()) !=
nullptr) {
110 output += buffer.data();
115 if (result != EXIT_SUCCESS) {
116 throw std::runtime_error(
117 fmt::format(
"External command '{}' failed: {}", command, output));
123#if defined(__linux) || defined(__unix)
124 const char *value = std::getenv(name.c_str());
126 if (value ==
nullptr)
129 return std::string{value};
130#elif defined(WINDOWS) || defined(_WIN32) || defined(WIN32)
131 static constexpr auto kMaxEnvLength = 2096U;
132 static char value[kMaxEnvLength];
134 GetEnvironmentVariableA(name.c_str(), value, kMaxEnvLength);
135 if (ret == 0 || ret > kMaxEnvLength)
146 const auto env =
get_env(
"CLANGUML_GIT_COMMIT");
156 catch (std::runtime_error &e) {
164 const std::string &cmd,
const std::string &env_override)
166 auto env =
get_env(env_override);
176 catch (std::runtime_error &e) {
186 "git rev-parse --abbrev-ref HEAD",
"CLANGUML_GIT_BRANCH");
192 "git describe --tags --always",
"CLANGUML_GIT_REVISION");
203 "git rev-parse --show-toplevel",
"CLANGUML_GIT_TOPLEVEL_DIR");
209 return "Windows, 32-bit";
211 return "Windows, 64-bit";
212#elif __has_include(<sys/utsname.h>)
215 return fmt::format(
"{} {} {}", utsn.sysname, utsn.machine, utsn.release);
218#elif __APPLE__ || __MACH__
222#elif __unix__ || __unix
229std::string
ltrim(
const std::string &s)
231 const size_t start = s.find_first_not_of(
WHITESPACE);
232 return (start == std::string::npos) ?
"" : s.substr(start);
235std::string
rtrim(
const std::string &s)
237 const size_t end = s.find_last_not_of(
WHITESPACE);
238 return (end == std::string::npos) ?
"" : s.substr(0, end + 1);
244 if (res.find(
"typename ") == 0)
245 return res.substr(strlen(
"typename "));
253 std::string str, std::string_view delimiter,
bool skip_empty)
255 std::vector<std::string> result;
259 result.push_back(std::move(str));
260 else if (!skip_empty)
261 result.push_back(std::move(str));
264 while (
static_cast<unsigned int>(!str.empty()) != 0U) {
265 auto index = str.find(delimiter);
266 if (index != std::string::npos) {
267 auto tok = str.substr(0, index);
269 result.push_back(std::move(tok));
270 else if (!skip_empty)
271 result.push_back(std::move(tok));
273 str = str.substr(index + delimiter.size());
277 result.push_back(std::move(str));
278 else if (!skip_empty)
279 result.push_back(std::move(str));
289 std::vector<std::string> result;
291 while (
static_cast<unsigned int>(!str.empty()) != 0U) {
292 auto index = std::find_if(
293 str.begin(), str.end(), [](
auto c) { return std::isspace(c); });
294 if (index != str.end()) {
295 auto tok = str.substr(0, std::distance(str.begin(), index));
297 result.push_back(std::move(tok));
298 str = str.substr(std::distance(str.begin(), index) + 1);
302 result.push_back(str);
310 const std::vector<std::string> &toks, std::string_view delimiter)
312 return fmt::format(
"{}", fmt::join(toks, delimiter));
315std::string
abbreviate(
const std::string &s,
const unsigned int max_length)
317 if (s.size() <= max_length)
322 res.resize(max_length);
324 if (res.size() > 3) {
325 res.replace(res.size() - 3, 3, 3,
'.');
332 const std::string &input, std::tuple<std::string, size_t, size_t> &result)
334 const std::regex alias_regex(R
"((@A\([^\).]+\)))");
337 std::sregex_iterator(input.begin(), input.end(), alias_regex);
338 auto end_it = std::sregex_iterator();
340 if (alias_it == end_it)
343 const std::smatch &match = *alias_it;
344 const std::string alias = match.str().substr(3, match.str().size() - 4);
346 std::get<0>(result) = alias;
347 std::get<1>(result) = match.position();
348 std::get<2>(result) = match.length();
354 const std::string &replace_with)
356 bool replaced{
false};
358 auto pos = input.find(pattern);
359 while (pos < input.size()) {
360 input.replace(pos, pattern.size(), replace_with);
361 pos = input.find(pattern, pos + replace_with.size());
370 const std::filesystem::path &path,
const std::filesystem::path &prefix)
372 auto normal_path = std::filesystem::path();
373 auto normal_prefix = std::filesystem::path();
375 for (
const auto &element : path.lexically_normal()) {
376 if (!element.empty())
377 normal_path /= element;
380 for (
const auto &element : prefix.lexically_normal()) {
381 if (!element.empty())
382 normal_prefix /= element;
385 auto normal_path_str = normal_path.string();
386 auto normal_prefix_str = normal_prefix.string();
387 return std::search(normal_path_str.begin(), normal_path_str.end(),
388 normal_prefix_str.begin(),
389 normal_prefix_str.end()) == normal_path_str.begin();
392template <>
bool starts_with(
const std::string &s,
const std::string &prefix)
394 return s.rfind(prefix, 0) == 0;
397template <>
bool ends_with(
const std::string &value,
const std::string &suffix)
399 if (suffix.size() > value.size())
401 return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
406 constexpr auto kSeedStart{0x6a3712b5};
407 constexpr auto kSeedShiftFirst{6};
408 constexpr auto kSeedShiftSecond{2};
410 return kSeedStart + (seed << kSeedShiftFirst) + (seed >> kSeedShiftSecond);
415 std::vector<std::string> path_tokens;
417 if (p.has_root_directory()) {
421 if (p.root_name().string().size() > 1) {
422 if (p.is_absolute()) {
423 path_tokens.push_back(std::string{
424 std::tolower(p.root_name().string().at(0), std::locale())});
432 for (; it != p.end(); it++)
433 path_tokens.push_back(it->string());
435 if (p.has_root_directory())
436 return fmt::format(
"/{}", fmt::join(path_tokens,
"/"));
438 return fmt::format(
"{}", fmt::join(path_tokens,
"/"));
442 const std::filesystem::path &p,
const std::filesystem::path &root)
447 auto result = root / p;
448 result = result.lexically_normal();
449 result.make_preferred();
455 const std::filesystem::path &child,
const std::filesystem::path &parent)
457 if (child.has_root_directory() != parent.has_root_directory())
460 return starts_with(weakly_canonical(child), weakly_canonical(parent));
478 unsigned current_line_length{0};
479 for (
const auto &token : tokens) {
480 if (current_line_length < width) {
485 result.back() =
'\n';
486 current_line_length = 0;
491 current_line_length += token.size() + 1;
501 if (path.is_absolute())
504 std::filesystem::path result;
506 for (
const auto &part : path) {
517 const std::filesystem::path &path,
const std::filesystem::path &base)
525 if (normalized_path == normalized_base)
528 auto rel = std::filesystem::relative(normalized_path, normalized_base);
530 std::string rel_str = rel.string();
531 return !rel_str.empty() && rel.native()[0] !=
'.';
535 const std::map<std::string, std::string> &m,
const std::string &path)
541 std::vector<std::string> keys;
542 keys.reserve(m.size());
543 for (
const auto &[key, pattern] : m) {
547 std::sort(keys.begin(), keys.end(),
548 [](
const std::string &a,
const std::string &b) {
549 return a.size() > b.size();
552 std::filesystem::path file_path{path};
554 for (
const auto &key : keys) {
555 const auto &pattern = m.at(key);
556 std::filesystem::path key_path{key};
559 return {{key, pattern}};
563 if ((path.empty() || file_path.is_relative() || path ==
".") &&
565 return {{
".", m.at(
".")}};