0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
util.h
Go to the documentation of this file.
1/**
2 * @file src/util/util.h
3 *
4 * Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18#pragma once
19
20#include <spdlog/sinks/stdout_color_sinks.h>
21#include <spdlog/spdlog.h>
22
23#include <algorithm>
24#include <cstring>
25#include <filesystem>
26#include <map>
27#include <optional>
28#include <string>
29#include <type_traits>
30#include <vector>
31
32#define LOG_ERROR(fmt__, ...) \
33 spdlog::get("clanguml-logger") \
34 ->error(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \
35 __LINE__, ##__VA_ARGS__)
36
37#define LOG_WARN(fmt__, ...) \
38 spdlog::get("clanguml-logger") \
39 ->warn(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \
40 __LINE__, ##__VA_ARGS__)
41
42#define LOG_INFO(fmt__, ...) \
43 spdlog::get("clanguml-logger") \
44 ->info(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \
45 __LINE__, ##__VA_ARGS__)
46
47#define LOG_DBG(fmt__, ...) \
48 spdlog::get("clanguml-logger") \
49 ->debug(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \
50 __LINE__, ##__VA_ARGS__)
51
52#define LOG_TRACE(fmt__, ...) \
53 spdlog::get("clanguml-logger") \
54 ->trace(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \
55 __LINE__, ##__VA_ARGS__)
56
57namespace clanguml::util {
58
59#define FILENAME_ \
60 (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
61
62constexpr unsigned kDefaultMessageCommentWidth{25U};
63
64/**
65 * @brief Left trim a string
66 *
67 * @param s Input string
68 * @return Left trimmed string
69 */
70std::string ltrim(const std::string &s);
71
72/**
73 * @brief Right trim a string
74 *
75 * @param s Input string
76 * @return Right trimmed string
77 */
78std::string rtrim(const std::string &s);
79
80/**
81 * @brief Trim a string
82 *
83 * @param s Input string
84 * @return Trimmed string
85 */
86std::string trim(const std::string &s);
87
88/**
89 * @brief Remove `typename` prefix from a string if exists
90 * @param s Input string
91 * @return String without `typename` prefix
92 */
93std::string trim_typename(const std::string &s);
94
95/**
96 * @brief Execute a shell `command` and return console output as string
97 *
98 * @param command Shell command to execute
99 * @return Console output of the command
100 */
101std::string get_process_output(const std::string &command);
102
103/**
104 * @brief Execute command shell and throw exception if command fails
105 *
106 * @param command Command to execute
107 */
108void check_process_output(const std::string &command);
109
110/**
111 * @brief Get value of an environment variable
112 *
113 * @param name Name of the environment variable
114 * @return Value of the environment variable, or empty if it doesn't exist
115 */
116std::string get_env(const std::string &name);
117
118/**
119 * @brief Check if `$PWD` is in a Git repository
120 *
121 * This can be overridden by exporting `CLANGUML_GIT_COMMIT` environment
122 * variable.
123 *
124 * @return True, if the current directory is in a Git repository
125 */
126bool is_git_repository();
127
128/**
129 * @brief Get current Git branch
130 *
131 * @return Name of the current Git branch
132 */
133std::string get_git_branch();
134
135/**
136 * @brief Get current Git revision
137 *
138 * Generates a Git revision tag using `git describe --tags --always` command
139 *
140 * @return Current repository Git revision
141 */
142std::string get_git_revision();
143
144/**
145 * @brief Get current Git commit
146 *
147 * @return Latest Git commit hash
148 */
149std::string get_git_commit();
150
151/**
152 * @brief Get path to the top level Git directory
153 *
154 * @return Absolut path to the nearest directory containing `.git` folder
155 */
156std::string get_git_toplevel_dir();
157
158/**
159 * @brief Get descriptive name of the current operating system.
160 *
161 * @return Name of the operating system
162 */
163std::string get_os_name();
164
165template <typename T, typename S>
166std::unique_ptr<T> unique_pointer_cast(std::unique_ptr<S> &&p) noexcept
167{
168 if (T *const converted = dynamic_cast<T *>(p.get())) {
169 std::move(p).release(); // NOLINT
170 return std::unique_ptr<T>{converted};
171 }
172
173 return {};
174}
175
176/**
177 * @brief Split a string using delimiter
178 *
179 * Basic string split function, because C++ stdlib does not have one.
180 * In case the string does not contain the delimiter, the original
181 * string is returned as the only element of the vector.
182 *
183 * @param str String to split
184 * @param delimiter Delimiter string
185 * @param skip_empty Skip empty toks between delimiters if true
186 *
187 * @return Vector of string tokens.
188 */
189std::vector<std::string> split(
190 std::string str, std::string_view delimiter, bool skip_empty = true);
191
192std::vector<std::string> split_isspace(std::string str);
193
194/**
195 * @brief Remove and erase elements from a vector
196 *
197 * @tparam T Element type
198 * @tparam F Functor type
199 * @param v Vector to remove elements from
200 * @param f Functor to decide which elements to remove
201 */
202template <typename T, typename F> void erase_if(std::vector<T> &v, F &&f)
203{
204 v.erase(std::remove_if(v.begin(), v.end(), std::forward<F>(f)), v.end());
205}
206
207/**
208 * @brief Join `toks` into string using `delimiter` as separator
209 *
210 * @param toks Elements to join into string
211 * @param delimiter Separator to use to join elements
212 * @return Concatenated elements into one string
213 */
214std::string join(
215 const std::vector<std::string> &toks, std::string_view delimiter);
216
217/**
218 * @brief Join `args` into string using `delimiter` as separator
219 *
220 * @tparam Args Element type
221 * @param delimiter Separator to use to join elements
222 * @param args Elements to join into string
223 * @return Arguments concatenated into one string
224 */
225template <typename... Args>
226std::string join(std::string_view delimiter, Args... args)
227{
228 std::vector<std::string> coll{args...};
229
230 erase_if(coll, [](const auto &s) {
231 return s.find_first_not_of(" \t") == std::string::npos;
232 });
233
234 return fmt::format("{}", fmt::join(coll, delimiter));
235}
236
237/**
238 * @brief Abbreviate string to max_length, and replace last 3 characters
239 * with ellipsis.
240 *
241 * @param s Input string
242 * @param max_length Maximum length
243 * @return Abbreviated string
244 */
245std::string abbreviate(const std::string &s, unsigned int max_length);
246
247/**
248 * @brief Find element alias in Puml note
249 *
250 * Finds aliases of the form @A(entity_name) in the Puml notes
251 * or directives.
252 * The match, if any, is returned in the result tuple:
253 * (entity_name, offset, length)
254 *
255 * @return True if match was found
256 */
258 const std::string &input, std::tuple<std::string, size_t, size_t> &result);
259
260/**
261 * @brief Find and replace in string
262 *
263 * Replaces all occurences of pattern with replace_with in input string.
264 *
265 * @return True if at least on replacement was made
266 */
267bool replace_all(std::string &input, const std::string &pattern,
268 const std::string &replace_with);
269
270/**
271 * @brief Appends a vector to a vector.
272 *
273 * @tparam T
274 * @param l
275 * @param r
276 */
277template <typename T> void append(std::vector<T> &l, const std::vector<T> &r)
278{
279 l.insert(l.end(), r.begin(), r.end());
280}
281
282/**
283 * @brief Checks if collection starts with a prefix.
284 *
285 * @tparam T e.g. std::vector<std::string>
286 * @param col Collection to be checked against prefix
287 * @param prefix Container, which specifies the prefix
288 * @return true if first prefix.size() elements of col are equal to prefix
289 */
290template <typename T> bool starts_with(const T &col, const T &prefix)
291{
292 if (prefix.size() > col.size())
293 return false;
294
295 return std::search(col.begin(), col.end(), prefix.begin(), prefix.end()) ==
296 col.begin();
297}
298
299template <>
300bool starts_with(
301 const std::filesystem::path &path, const std::filesystem::path &prefix);
302
303template <> bool starts_with(const std::string &s, const std::string &prefix);
304
305template <typename T> bool ends_with(const T &value, const T &suffix);
306
307template <> bool ends_with(const std::string &value, const std::string &suffix);
308
309template <typename T>
310bool ends_with(const std::vector<T> &col, const std::vector<T> &suffix)
311{
312 if (suffix.size() > col.size())
313 return false;
314
315 return std::vector<std::string>(suffix.rbegin(), suffix.rend()) ==
316 std::vector<std::string>(col.rbegin(), col.rbegin() + suffix.size());
317}
318
319/**
320 * @brief Removes prefix sequence of elements from the beginning of col.
321 *
322 * @tparam T
323 * @param col
324 * @param prefix
325 */
326template <typename T>
327void remove_prefix(std::vector<T> &col, const std::vector<T> &prefix)
328{
329 if (!starts_with(col, prefix))
330 return;
331
332 col = std::vector<T>(col.begin() + prefix.size(), col.end());
333}
334
335/**
336 * Returns true if element exists in container.
337 *
338 * @tparam T
339 * @tparam E
340 * @param container
341 * @param element
342 * @return
343 */
344template <typename T, typename E>
345bool contains(const T &container, const E &element)
346{
347 if constexpr (std::is_pointer_v<E>) {
348 return std::find_if(container.begin(), container.end(),
349 [&element](const auto &e) { return *e == *element; }) !=
350 container.end();
351 }
352 else if constexpr (std::is_same_v<std::remove_cv_t<T>, std::string>) {
353 return container.find(element) != std::string::npos;
354 }
355 else {
356 return std::find(container.begin(), container.end(), element) !=
357 container.end();
358 }
359}
360
361template <typename T, typename F> void for_each(const T &collection, F &&func)
362{
363 std::for_each(std::begin(collection), std::end(collection),
364 std::forward<decltype(func)>(func));
365}
366
367template <typename T, typename C, typename F>
368void for_each_if(const T &collection, C &&cond, F &&func)
369{
370 std::for_each(std::begin(collection), std::end(collection),
371 [cond = std::forward<decltype(cond)>(cond),
372 func = std::forward<decltype(func)>(func)](const auto &e) {
373 if (cond(e))
374 func(e);
375 });
376}
377
378template <typename R, typename T, typename F>
379std::vector<R> map(const std::vector<T> &in, F &&f)
380{
381 std::vector<R> out;
382 std::transform(
383 in.cbegin(), in.cend(), std::back_inserter(out), std::forward<F>(f));
384 return out;
385}
386
387template <typename T, typename F, typename FElse>
388void if_not_null(const T *pointer, F &&func, FElse &&func_else)
389{
390 if (pointer != nullptr) {
391 std::forward<F>(func)(pointer);
392 }
393 else {
394 std::forward<FElse>(func_else)();
395 }
396}
397
398template <typename T, typename F> void if_not_null(const T *pointer, F &&func)
399{
400 if_not_null(pointer, std::forward<F>(func), []() {});
401}
402
403template <typename F, typename FElse>
404void _if(const bool condition, F &&func, FElse &&func_else)
405{
406 if (condition) {
407 std::forward<F>(func)();
408 }
409 else {
410 std::forward<FElse>(func_else)();
411 }
412}
413
414template <typename F> void _if(const bool condition, F &&func)
415{
416 _if(condition, std::forward<F>(func), []() {});
417}
418
419/**
420 * @brief Generate a hash seed.
421 *
422 * @param seed Initial seed.
423 * @return Hash seed.
424 */
425std::size_t hash_seed(std::size_t seed);
426
427/**
428 * @brief Convert filesystem path to url path
429 *
430 * The purpose of this function is to make sure that a path can
431 * be used in a URL, e.g. it's separators are POSIX-style.
432 *
433 * @param p Path to convert
434 * @return String representation of the path in URL format
435 */
436std::string path_to_url(const std::filesystem::path &p);
437
438/**
439 * @brief Ensure path is absolute.
440 *
441 * If path is absolute, return the p. If path is not absolute, make it
442 * absolute with respect to root directory.
443 *
444 * @param p Path to modify
445 * @param root Root against which the path should be made absolute
446 * @return Absolute path
447 */
448std::filesystem::path ensure_path_is_absolute(const std::filesystem::path &p,
449 const std::filesystem::path &root = std::filesystem::current_path());
450
451/**
452 * @brief Check if a given path is relative to another path.
453 *
454 * @param parent The path to be checked for relativity.
455 * @param right The base path against which the relativity is checked.
456 * @return True if the child path is relative to the parent path, false
457 * otherwise.
458 */
459bool is_relative_to(
460 const std::filesystem::path &parent, const std::filesystem::path &child);
461
462std::string format_message_comment(
463 const std::string &c, unsigned width = kDefaultMessageCommentWidth);
464
465bool is_subpath(
466 const std::filesystem::path &path, const std::filesystem::path &prefix);
467
468std::optional<std::pair<std::string, std::string>> find_entry_by_path_prefix(
469 const std::map<std::string, std::string> &m, const std::string &prefix);
470} // namespace clanguml::util