/** * @file src/util/util.h * * Copyright (c) 2021-2023 Bartek Kryza * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #define LOG_ERROR(fmt__, ...) \ spdlog::get("clanguml-logger") \ ->error(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \ __LINE__, ##__VA_ARGS__) #define LOG_WARN(fmt__, ...) \ spdlog::get("clanguml-logger") \ ->warn(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \ __LINE__, ##__VA_ARGS__) #define LOG_INFO(fmt__, ...) \ spdlog::get("clanguml-logger") \ ->info(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \ __LINE__, ##__VA_ARGS__) #define LOG_DBG(fmt__, ...) \ spdlog::get("clanguml-logger") \ ->debug(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \ __LINE__, ##__VA_ARGS__) #define LOG_TRACE(fmt__, ...) \ spdlog::get("clanguml-logger") \ ->trace(fmt::runtime(std::string("[{}:{}] ") + fmt__), FILENAME_, \ __LINE__, ##__VA_ARGS__) namespace clanguml::util { #define FILENAME_ \ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) constexpr unsigned kDefaultMessageCommentWidth{25U}; /** * @brief Left trim a string * * @param s Input string * @return Left trimmed string */ std::string ltrim(const std::string &s); /** * @brief Right trim a string * * @param s Input string * @return Right trimmed string */ std::string rtrim(const std::string &s); /** * @brief Trim a string * * @param s Input string * @return Trimmed string */ std::string trim(const std::string &s); /** * @brief Remove `typename` prefix from a string if exists * @param s Input string * @return String without `typename` prefix */ std::string trim_typename(const std::string &s); /** * @brief Execute a shell `command` and return console output as string * * @param command Shell command to execute * @return Console output of the command */ std::string get_process_output(const std::string &command); /** * @brief Execute command shell and throw exception if command fails * * @param command Command to execute */ void check_process_output(const std::string &command); /** * @brief Get value of an environment variable * * @param name Name of the environment variable * @return Value of the environment variable, or empty if it doesn't exist */ std::string get_env(const std::string &name); /** * @brief Check if `$PWD` is in a Git repository * * This can be overridden by exporting `CLANGUML_GIT_COMMIT` environment * variable. * * @return True, if the current directory is in a Git repository */ bool is_git_repository(); /** * @brief Get current Git branch * * @return Name of the current Git branch */ std::string get_git_branch(); /** * @brief Get current Git revision * * Generates a Git revision tag using `git describe --tags --always` command * * @return Current repository Git revision */ std::string get_git_revision(); /** * @brief Get current Git commit * * @return Latest Git commit hash */ std::string get_git_commit(); /** * @brief Get path to the top level Git directory * * @return Absolut path to the nearest directory containing `.git` folder */ std::string get_git_toplevel_dir(); /** * @brief Get descriptive name of the current operating system. * * @return Name of the operating system */ std::string get_os_name(); /** * @brief Split a string using delimiter * * Basic string split function, because C++ stdlib does not have one. * In case the string does not contain the delimiter, the original * string is returned as the only element of the vector. * * @param str String to split * @param delimiter Delimiter string * @param skip_empty Skip empty toks between delimiters if true * * @return Vector of string tokens. */ std::vector split( std::string str, std::string_view delimiter, bool skip_empty = true); std::vector split_isspace(std::string str); /** * @brief Remove and erase elements from a vector * * @tparam T Element type * @tparam F Functor type * @param v Vector to remove elements from * @param f Functor to decide which elements to remove */ template void erase_if(std::vector &v, F &&f) { v.erase(std::remove_if(v.begin(), v.end(), std::forward(f)), v.end()); } /** * @brief Join `toks` into string using `delimiter` as separator * * @param toks Elements to join into string * @param delimiter Separator to use to join elements * @return Concatenated elements into one string */ std::string join( const std::vector &toks, std::string_view delimiter); /** * @brief Join `args` into string using `delimiter` as separator * * @tparam Args Element type * @param delimiter Separator to use to join elements * @param args Elements to join into string * @return Arguments concatenated into one string */ template std::string join(std::string_view delimiter, Args... args) { std::vector coll{args...}; erase_if(coll, [](const auto &s) { return s.find_first_not_of(" \t") == std::string::npos; }); return fmt::format("{}", fmt::join(coll, delimiter)); } /** * @brief Abbreviate string to max_length, and replace last 3 characters * with ellipsis. * * @param s Input string * @param max_length Maximum length * @return Abbreviated string */ std::string abbreviate(const std::string &s, unsigned int max_length); /** * @brief Find element alias in Puml note * * Finds aliases of the form @A(entity_name) in the Puml notes * or directives. * The match, if any, is returned in the result tuple: * (entity_name, offset, length) * * @return True if match was found */ bool find_element_alias( const std::string &input, std::tuple &result); /** * @brief Find and replace in string * * Replaces all occurences of pattern with replace_with in input string. * * @return True if at least on replacement was made */ bool replace_all(std::string &input, const std::string &pattern, const std::string &replace_with); /** * @brief Appends a vector to a vector. * * @tparam T * @param l * @param r */ template void append(std::vector &l, const std::vector &r) { l.insert(l.end(), r.begin(), r.end()); } /** * @brief Checks if collection starts with a prefix. * * @tparam T e.g. std::vector * @param col Collection to be checked against prefix * @param prefix Container, which specifies the prefix * @return true if first prefix.size() elements of col are equal to prefix */ template bool starts_with(const T &col, const T &prefix) { if (prefix.size() > col.size()) return false; return std::search(col.begin(), col.end(), prefix.begin(), prefix.end()) == col.begin(); } template <> bool starts_with( const std::filesystem::path &path, const std::filesystem::path &prefix); template <> bool starts_with(const std::string &s, const std::string &prefix); template bool ends_with(const T &value, const T &suffix); template <> bool ends_with(const std::string &value, const std::string &suffix); template bool ends_with(const std::vector &col, const std::vector &suffix) { if (suffix.size() > col.size()) return false; return std::vector(suffix.rbegin(), suffix.rend()) == std::vector(col.rbegin(), col.rbegin() + suffix.size()); } /** * @brief Removes prefix sequence of elements from the beginning of col. * * @tparam T * @param col * @param prefix */ template void remove_prefix(std::vector &col, const std::vector &prefix) { if (!starts_with(col, prefix)) return; col = std::vector(col.begin() + prefix.size(), col.end()); } /** * Returns true if element exists in container. * * @tparam T * @tparam E * @param container * @param element * @return */ template bool contains(const T &container, const E &element) { if constexpr (std::is_pointer_v) { return std::find_if(container.begin(), container.end(), [&element](const auto &e) { return *e == *element; }) != container.end(); } else if constexpr (std::is_same_v, std::string>) { return container.find(element) != std::string::npos; } else { return std::find(container.begin(), container.end(), element) != container.end(); } } template void for_each(const T &collection, F &&func) { std::for_each(std::begin(collection), std::end(collection), std::forward(func)); } template void for_each_if(const T &collection, C &&cond, F &&func) { std::for_each(std::begin(collection), std::end(collection), [cond = std::forward(cond), func = std::forward(func)](const auto &e) { if (cond(e)) func(e); }); } template std::vector map(const std::vector &in, F &&f) { std::vector out; std::transform( in.cbegin(), in.cend(), std::back_inserter(out), std::forward(f)); return out; } template void if_not_null(const T *pointer, F &&func, FElse &&func_else) { if (pointer != nullptr) { std::forward(func)(pointer); } else { std::forward(func_else)(); } } template void if_not_null(const T *pointer, F &&func) { if_not_null(pointer, std::forward(func), []() {}); } template void _if(const bool condition, F &&func, FElse &&func_else) { if (condition) { std::forward(func)(); } else { std::forward(func_else)(); } } template void _if(const bool condition, F &&func) { _if(condition, std::forward(func), []() {}); } /** * @brief Generate a hash seed. * * @param seed Initial seed. * @return Hash seed. */ std::size_t hash_seed(std::size_t seed); /** * @brief Convert filesystem path to url path * * The purpose of this function is to make sure that a path can * be used in a URL, e.g. it's separators are POSIX-style. * * @param p Path to convert * @return String representation of the path in URL format */ std::string path_to_url(const std::filesystem::path &p); /** * @brief Ensure path is absolute. * * If path is absolute, return the p. If path is not absolute, make it * absolute with respect to root directory. * * @param p Path to modify * @param root Root against which the path should be made absolute * @return Absolute path */ std::filesystem::path ensure_path_is_absolute(const std::filesystem::path &p, const std::filesystem::path &root = std::filesystem::current_path()); std::string format_message_comment( const std::string &c, unsigned width = kDefaultMessageCommentWidth); } // namespace clanguml::util