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