/* ** Copyright (C) 2020-2023 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License ** as published by the Free Software Foundation; either version 2.1 ** of the License, or (at your option) any later version. ** ** This library is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free ** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA ** 02110-1301, USA. */ #ifndef MU_UTILS_HH__ #define MU_UTILS_HH__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mu-utils-format.hh" #include "mu-option.hh" #ifndef FMT_HEADER_ONLY #define FMT_HEADER_ONLY #endif /*FMT_HEADER_ONLY*/ #include #include namespace Mu { /* * Separator characters used in various places; importantly, * they are not used in UTF-8 */ constexpr const auto SepaChar1 = '\xfe'; constexpr const auto SepaChar2 = '\xff'; /* * Logging/printing/formatting functions connect libfmt with the Glib logging * system. We wrap so perhaps at some point (C++23?) we can use std:: instead. */ /* * Debug/error/warning logging * * The 'noexcept' means that they _wilL_ terminate the program * when the formatting fails (ie. a bug) */ template void mu_debug(fmt::format_string frm, T&&... args) noexcept { g_log("mu", G_LOG_LEVEL_DEBUG, "%s", fmt::format(frm, std::forward(args)...).c_str()); } template void mu_info(fmt::format_string frm, T&&... args) noexcept { g_log("mu", G_LOG_LEVEL_INFO, "%s", fmt::format(frm, std::forward(args)...).c_str()); } template void mu_message(fmt::format_string frm, T&&... args) noexcept { g_log("mu", G_LOG_LEVEL_MESSAGE, "%s", fmt::format(frm, std::forward(args)...).c_str()); } template void mu_warning(fmt::format_string frm, T&&... args) noexcept { g_log("mu", G_LOG_LEVEL_WARNING, "%s", fmt::format(frm, std::forward(args)...).c_str()); } template void mu_critical(fmt::format_string frm, T&&... args) noexcept { g_log("mu", G_LOG_LEVEL_CRITICAL, "%s", fmt::format(frm, std::forward(args)...).c_str()); } template void mu_error(fmt::format_string frm, T&&... args) noexcept { g_log("mu", G_LOG_LEVEL_ERROR, "%s", fmt::format(frm, std::forward(args)...).c_str()); } /* * Printing */ template void mu_print(fmt::format_string frm, T&&... args) noexcept { fmt::print(frm, std::forward(args)...); } template void mu_println(fmt::format_string frm, T&&... args) noexcept { fmt::println(frm, std::forward(args)...); } template void mu_printerr(fmt::format_string frm, T&&... args) noexcept { fmt::print(stderr, frm, std::forward(args)...); } template void mu_printerrln(fmt::format_string frm, T&&... args) noexcept { fmt::println(stderr, frm, std::forward(args)...); } /* * Fprmatting */ template std::string mu_format(fmt::format_string frm, T&&... args) noexcept { return fmt::format(frm, std::forward(args)...); } template auto mu_join(Range&& range, std::string_view sepa) { return fmt::join(std::forward(range), sepa); } using StringVec = std::vector; /** * Flatten a string -- downcase and fold diacritics etc. * * @param str a string * * @return a flattened string */ std::string utf8_flatten(const char* str); inline std::string utf8_flatten(const std::string& s) { return utf8_flatten(s.c_str()); } /** * Replace all control characters with spaces, and remove leading and trailing space. * * @param dirty an unclean string * * @return a cleaned-up string. */ std::string utf8_clean(const std::string& dirty); /** * Remove ctrl characters, replacing them with ' '; subsequent * ctrl characters are replaced by a single ' ' * * @param str a string * * @return the string without control characters */ std::string remove_ctrl(const std::string& str); /** * Split a string in parts. As a special case, splitting an empty string * yields an empty vector (not a vector with a single empty element) * * @param str a string * @param sepa the separator * * @return the parts. */ std::vector split(const std::string& str, const std::string& sepa); /** * Split a string in parts. As a special case, splitting an empty string * yields an empty vector (not a vector with a single empty element) * * @param str a string * @param sepa the separator * * @return the parts. */ std::vector split(const std::string& str, char sepa); /** * Join the strings in svec into a string, separated by sepa * * @param svec a string vector * @param sepa separator * * @return string */ std::string join(const std::vector& svec, const std::string& sepa); static inline std::string join(const std::vector& svec, char sepa) { return join(svec, std::string(1, sepa)); } /** * write a string (assumed to be in utf8-format) to a stream, * converted to the current locale * * @param str a string * @param stream a stream * * @return true if printing worked, false otherwise */ bool fputs_encoded (const std::string& str, FILE *stream); /** * print a fmt-style formatted string (assumed to be in utf8-format) to stdout, * converted to the current locale * * @param a standard fmt-style format string, followed by a parameter list * * @return true if printing worked, false otherwise */ template static inline bool mu_print_encoded(fmt::format_string frm, T&&... args) noexcept { return fputs_encoded(fmt::format(frm, std::forward(args)...), ::stdout); } /** * Parse a date string to the corresponding time_t * * * @param date the date expressed a YYYYMMDDHHMMSS or any n... of the first * characters, using the local timezone. Non-digits are ignored, * so 2018-05-05 is equivalent to 20180505. * @param first whether to fill out incomplete dates to the start (@true) or the * end (@false); ie. either 1972 -> 197201010000 or 1972 -> 197212312359 * * @return the corresponding time_t or Nothing if parsing failed. */ Option parse_date_time(const std::string& date, bool first); /** * 64-bit incarnation of time_t expressed as a 10-digit string. Uses 64-bit for the time-value, * regardless of the size of time_t. * * @param t some time value * * @return */ std::string date_to_time_t_string(int64_t t); /** * Get a string for a given time_t and format * memory that must be freed after use. * * @param frm the format of the string (in strftime(3) format) * @param t the time as time_t * @param utc whether to display as UTC(if true) or local time * * @return a string representation of the time in UTF8-format, or empty in case * of error. */ std::string time_to_string(const char *frm, time_t t, bool utc = false) G_GNUC_CONST; /** * Hack to avoid locale crashes * * @return true if setting locale worked; false otherwise */ bool locale_workaround(); /** * Is the given timezone available? For tests * * @param tz a timezone, such as Europe/Helsinki * * @return true or false */ bool timezone_available(const std::string& tz); // https://stackoverflow.com/questions/19053351/how-do-i-use-a-custom-deleter-with-a-stdunique-ptr-member template struct deleter_from_fn { template constexpr void operator()(T* arg) const { fn(arg); } }; template using deletable_unique_ptr = std::unique_ptr>; using Clock = std::chrono::steady_clock; using Duration = Clock::duration; template constexpr int64_t to_unit(Duration d) { using namespace std::chrono; return duration_cast(d).count(); } constexpr int64_t to_s(Duration d) { return to_unit(d); } constexpr int64_t to_ms(Duration d) { return to_unit(d); } constexpr int64_t to_us(Duration d) { return to_unit(d); } struct StopWatch { using Clock = std::chrono::steady_clock; StopWatch(const std::string name) : start_{Clock::now()}, name_{name} {} ~StopWatch() { const auto us{static_cast(to_us(Clock::now() - start_))}; if (us > 2000000) mu_debug("sw: {}: finished after {:.1f} s", name_, us / 1000000); else if (us > 2000) mu_debug("sw: {}: finished after {:.1f} ms", name_, us / 1000); else mu_debug("sw: {}: finished after {} us", name_, us); } private: Clock::time_point start_; std::string name_; }; /** * Convert a size string to a size in bytes * * @param sizestr the size string * @param first * * @return the size or Nothing if parsing failed */ Option parse_size(const std::string& sizestr, bool first); /** * Convert a size into a size in bytes string * * @param size the size * @param first * * @return the size expressed as a string with the decimal number of bytes */ std::string size_to_string(int64_t size); /** * get a crude 'summary' of the string, ie. the first /n/ lines of the strings, * with all newlines removed, replaced by single spaces * * @param str the source string * @param max_lines the maximum number of lines to include in the summary * * @return a newly allocated string with the summary. use g_free to free it. */ std::string summarize(const std::string& str, size_t max_lines); /** * Convert any ostreamable<< value to a string * * @param t the value * * @return a std::string */ template static inline std::string to_string(const T& val) { std::stringstream sstr; sstr << val; return sstr.str(); } /** * Consume a gchar and return a std::string * * @param str a gchar* (consumed/freed) * * @return a std::string, empty if gchar was {} */ static inline std::string to_string_gchar(gchar*&& str) { std::string s(str?str:""); g_free(str); return s; } /* * Lexnums are lexicographically sortable string representations of non-negative * integers. Start with 'f' + length of hex-representation number, followed by * the hex representation itself. So, * * 0 -> 'g0' * 1 -> 'g1' * 10 -> 'ga' * 16 -> 'h10' * * etc. */ std::string to_lexnum(int64_t val); int64_t from_lexnum(const std::string& str); /** * Like std::find_if, but using sequence instead of a range. * * @param seq some std::find_if compatible sequence * @param pred a predicate * * @return an iterator */ template typename Sequence::const_iterator seq_find_if(const Sequence& seq, UnaryPredicate pred) { return std::find_if(seq.cbegin(), seq.cend(), pred); } /** * Is pred(element) true for at least one element of sequence? * * @param seq sequence * @param pred a predicate * * @return true or false */ template bool seq_some(const Sequence& seq, UnaryPredicate pred) { return seq_find_if(seq, pred) != seq.cend(); } /** * Create a sequence that has all element of seq for which pred is true * * @param seq sequence * @param pred false * * @return sequence */ template Sequence seq_filter(const Sequence& seq, UnaryPredicate pred) { Sequence res; std::copy_if(seq.begin(), seq.end(), std::back_inserter(res), pred); return res; } /** * Create a sequence that has all element of seq for which pred is false * * @param seq sequence * @param pred false * * @return sequence */ template Sequence seq_remove(const Sequence& seq, UnaryPredicate pred) { Sequence res; std::remove_copy_if(seq.begin(), seq.end(), std::back_inserter(res), pred); return res; } template void seq_sort(Sequence& seq, Compare cmp) { std::sort(seq.begin(), seq.end(), cmp); } /** * Like std::accumulate, but using a sequence instead of a range. * * @param seq some std::accumulate compatible sequence * @param init the initial value * @param op binary operation to calculate the next element * * @return the result value. */ template ResultType seq_fold(const Sequence& seq, ResultType init, BinaryOp op) { return std::accumulate(seq.cbegin(), seq.cend(), init, op); } template void seq_for_each(const Sequence& seq, UnaryOp op) { std::for_each(seq.cbegin(), seq.cend(), op); } struct MaybeAnsi { explicit MaybeAnsi(bool use_color) : color_{use_color} {} enum struct Color { Black = 30, Red = 31, Green = 32, Yellow = 33, Blue = 34, Magenta = 35, Cyan = 36, White = 37, BrightBlack = 90, BrightRed = 91, BrightGreen = 92, BrightYellow = 93, BrightBlue = 94, BrightMagenta = 95, BrightCyan = 96, BrightWhite = 97, }; std::string fg(Color c) const { return ansi(c, true); } std::string bg(Color c) const { return ansi(c, false); } std::string reset() const { return color_ ? "\x1b[0m" : ""; } private: std::string ansi(Color c, bool fg = true) const { return color_ ? mu_format("\x1b[{}m", static_cast(c) + (fg ? 0 : 10)) : ""; } const bool color_; }; #define MU_COLOR_RED "\x1b[31m" #define MU_COLOR_GREEN "\x1b[32m" #define MU_COLOR_YELLOW "\x1b[33m" #define MU_COLOR_BLUE "\x1b[34m" #define MU_COLOR_MAGENTA "\x1b[35m" #define MU_COLOR_CYAN "\x1b[36m" #define MU_COLOR_DEFAULT "\x1b[0m" /// Allow using enum structs as bitflags #define MU_TO_NUM(ET, ELM) std::underlying_type_t(ELM) #define MU_TO_ENUM(ET, NUM) static_cast(NUM) #define MU_ENABLE_BITOPS(ET) \ constexpr ET operator&(ET e1, ET e2) { \ return MU_TO_ENUM(ET, MU_TO_NUM(ET, e1) & MU_TO_NUM(ET, e2)); \ } \ constexpr ET operator|(ET e1, ET e2) { \ return MU_TO_ENUM(ET, MU_TO_NUM(ET, e1) | MU_TO_NUM(ET, e2)); \ } \ constexpr ET operator~(ET e) { return MU_TO_ENUM(ET, ~(MU_TO_NUM(ET, e))); } \ constexpr bool any_of(ET e) { return MU_TO_NUM(ET, e) != 0; } \ constexpr bool none_of(ET e) { return MU_TO_NUM(ET, e) == 0; } \ constexpr bool one_of(ET e1, ET e2) { return (e1 & e2) == e2; } \ constexpr ET& operator&=(ET& e1, ET e2) { return e1 = e1 & e2; } \ constexpr ET& operator|=(ET& e1, ET e2) { return e1 = e1 | e2; } \ static_assert(1==1) // require a semicolon } // namespace Mu #endif /* MU_UTILS_HH__ */