diff --git a/lib/utils/mu-utils.cc b/lib/utils/mu-utils.cc index 9840c83f..242ab020 100644 --- a/lib/utils/mu-utils.cc +++ b/lib/utils/mu-utils.cc @@ -686,32 +686,3 @@ Mu::fputs_encoded (const std::string& str, FILE *stream) return (rv == EOF) ? false: true; } - -__attribute__((format(printf, 2, 0))) -static bool -print_args (FILE *stream, const char *frm, va_list args) -{ - char *str; - gboolean rv; - - str = g_strdup_vprintf (frm, args); - rv = fputs_encoded (str, stream); - g_free (str); - - return rv; -} - -bool -Mu::print_encoded (const char *frm, ...) -{ - va_list args; - gboolean rv; - - g_return_val_if_fail (frm, false); - - va_start (args, frm); - rv = print_args (stdout, frm, args); - va_end (args); - - return rv; -} diff --git a/lib/utils/mu-utils.hh b/lib/utils/mu-utils.hh index 0d7a5b1e..ca8461fe 100644 --- a/lib/utils/mu-utils.hh +++ b/lib/utils/mu-utils.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2020-2022 Dirk-Jan C. Binnema +** 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 @@ -107,6 +107,7 @@ inline void mu_printerrln(fmt::format_string frm, T&&... args) noexcept { fmt::println(stderr, frm, std::forward(args)...); } + /* * Fprmatting */ @@ -197,22 +198,27 @@ static inline std::string join(const std::vector& svec, char sepa) bool fputs_encoded (const std::string& str, FILE *stream); /** - * print a formatted string (assumed to be in utf8-format) to stdout, + * print a fmt-style formatted string (assumed to be in utf8-format) to stdout, * converted to the current locale * - * @param a standard printf() format string, followed by a parameter list + * @param a standard fmt-style format string, followed by a parameter list * * @return true if printing worked, false otherwise */ -bool print_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2); +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. - * @param first whether to fill out incomplete dates to the start or the end; - * ie. either 1972 -> 197201010000 or 1972 -> 197212312359 + * 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. */ @@ -302,17 +308,15 @@ to_us(Duration d) struct StopWatch { using Clock = std::chrono::steady_clock; StopWatch(const std::string name) : start_{Clock::now()}, name_{name} {} - ~StopWatch() - { + ~StopWatch() { const auto us{static_cast(to_us(Clock::now() - start_))}; if (us > 2000000) - g_debug("sw: %s: finished after %0.1f s", name_.c_str(), us / 1000000); + mu_debug("sw: {}: finished after {:.1f} s", name_, us / 1000000); else if (us > 2000) - g_debug("sw: %s: finished after %0.1f ms", name_.c_str(), us / 1000); + mu_debug("sw: {}: finished after {:.1f} ms", name_, us / 1000); else - g_debug("sw: %s: finished after %g us", name_.c_str(), us); + mu_debug("sw: {}: finished after {} us", name_, us); } - private: Clock::time_point start_; std::string name_; @@ -383,9 +387,9 @@ to_string_gchar(gchar*&& str) /* - * Lexicals Number are lexicographically sortable string representations - * of numbers. Start with 'g' + length of number in hex, followed by - * the ascii for the hex represntation. So, + * 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' @@ -476,54 +480,6 @@ void seq_for_each(const Sequence& seq, UnaryOp op) { std::for_each(seq.cbegin(), seq.cend(), op); } -/** - * array of associated pair elements -- like an alist - * but based on std::array and thus can be constexpr - */ -template - using AssocPairs = std::array, N>; - -/** - * Get the first value of the pair where the second element is @param s. - * - * @param p AssocPairs - * @param s some second pair value - * - * @return the matching first pair value, or Nothing if not found. - */ -template -constexpr Option -to_first(const P& p, typename P::value_type::second_type s) -{ - for (const auto& item: p) - if (item.second == s) - return item.first; - return Nothing; -} - -/** - * Get the second value of the pair where the first element is @param f. - * - * @param p AssocPairs - * @param f some first pair value - * - * @return the matching second pair value, or Nothing if not found. - */ -template -constexpr Option -to_second(const P& p, typename P::value_type::first_type f) -{ - for (const auto& item: p) - if (item.first == f) - return item.second; - return Nothing; -} - -/** - * Convert string view in something printable with %.*s - */ -#define STR_V(sv__) static_cast((sv__).size()), (sv__).data() - struct MaybeAnsi { explicit MaybeAnsi(bool use_color) : color_{use_color} {} @@ -555,7 +511,8 @@ struct MaybeAnsi { private: std::string ansi(Color c, bool fg = true) const { - return color_ ? format("\x1b[%dm", static_cast(c) + (fg ? 0 : 10)) : ""; + return color_ ? mu_format("\x1b[{}m", + static_cast(c) + (fg ? 0 : 10)) : ""; } const bool color_; @@ -574,12 +531,10 @@ private: #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) \ - { \ + 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) \ - { \ + 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))); } \ diff --git a/lib/utils/tests/test-utils.cc b/lib/utils/tests/test-utils.cc index 56b3d4de..5ba2fdae 100644 --- a/lib/utils/tests/test-utils.cc +++ b/lib/utils/tests/test-utils.cc @@ -282,35 +282,6 @@ test_locale_workaround() g_assert_true(locale_workaround()); } -enum struct TestEnum { A, B, C }; -constexpr AssocPairs -test_epairs = {{ - {TestEnum::A, "a"}, - {TestEnum::B, "b"}, - {TestEnum::C, "c"}, -}}; - -static constexpr Option -to_name(TestEnum te) -{ - return to_second(test_epairs, te); -} - -static constexpr Option -to_type(std::string_view name) -{ - return to_first(test_epairs, name); - -} - -static void -test_enum_pairs(void) -{ - assert_equal(to_name(TestEnum::A).value(), "a"); - g_assert_true(to_type("c").value() == TestEnum::C); -} - - static void test_summarize(void) @@ -353,7 +324,6 @@ main(int argc, char* argv[]) g_test_add_func("/utils/define-bitmap", test_define_bitmap); g_test_add_func("/utils/to-from-lexnum", test_to_from_lexnum); g_test_add_func("/utils/locale-workaround", test_locale_workaround); - g_test_add_func("/utils/enum-pairs", test_enum_pairs); return g_test_run(); } diff --git a/mu/mu-cmd-cfind.cc b/mu/mu-cmd-cfind.cc index e0151df0..962d0c7e 100644 --- a/mu/mu-cmd-cfind.cc +++ b/mu/mu-cmd-cfind.cc @@ -112,14 +112,10 @@ output_plain(ItemType itype, OptContact contact, const Options& opts) const auto col2{opts.nocolor ? "" : MU_COLOR_GREEN}; const auto coldef{opts.nocolor ? "" : MU_COLOR_DEFAULT}; - print_encoded("%s%s%s%s%s%s%s\n", - col1, - contact->name.c_str(), - coldef, - contact->name.empty() ? "" : " ", - col2, - contact->email.c_str(), - coldef); + mu_print_encoded("{}{}{}{}{}{}{}\n", + col1, contact->name, coldef, + contact->name.empty() ? "" : " ", + col2, contact->email, coldef); } static void @@ -129,8 +125,8 @@ output_mutt_alias(ItemType itype, OptContact contact, const Options& opts) return; const auto nick{guess_nick(*contact)}; - print_encoded("alias %s %s <%s>\n", nick.c_str(), - contact->name.c_str(), contact->email.c_str()); + mu_print_encoded("alias {} {} <{}>\n", nick, contact->name, contact->email); + } static void @@ -139,12 +135,8 @@ output_mutt_address_book(ItemType itype, OptContact contact, const Options& opts if (itype == ItemType::Header) mu_print ("Matching addresses in the mu database:\n"); - if (!contact) - return; - - print_encoded("%s\t%s\t\n", - contact->email.c_str(), - contact->name.c_str()); + if (contact) + mu_print_encoded("{}\t{}\t\n", contact->email, contact->name); } static void @@ -155,10 +147,8 @@ output_wanderlust(ItemType itype, OptContact contact, const Options& opts) auto nick=guess_nick(*contact); - print_encoded("%s \"%s\" \"%s\"\n", - contact->email.c_str(), - nick.c_str(), - contact->name.c_str()); + mu_print_encoded("{} \"{}\" \"{}\"\n", contact->email, nick, contact->name); + } static void @@ -167,9 +157,8 @@ output_org_contact(ItemType itype, OptContact contact, const Options& opts) if (!contact || contact->name.empty()) return; - print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n", - contact->name.c_str(), - contact->email.c_str()); + mu_print_encoded("* {}\n:PROPERTIES:\n:EMAIL: {}\n:END:\n\n", + contact->name, contact->email); } static void @@ -196,9 +185,9 @@ output_csv(ItemType itype, OptContact contact, const Options& opts) if (!contact) return; - print_encoded("%s,%s\n", - contact->name.empty() ? "" : Mu::quote(contact->name).c_str(), - Mu::quote(contact->email).c_str()); + mu_print_encoded("{},{}\n", + contact->name.empty() ? "" : Mu::quote(contact->name), + Mu::quote(contact->email)); } static void @@ -211,22 +200,22 @@ output_json(ItemType itype, OptContact contact, const Options& opts) mu_println (" {{"); const std::string name = contact->name.empty() ? "null" : Mu::quote(contact->name); - print_encoded( - " \"email\" : \"%s\",\n" - " \"name\" : %s,\n" - " \"display\" : %s,\n" - " \"last-seen\" : %" PRId64 ",\n" - " \"last-seen-iso\" : \"%s\",\n" - " \"personal\" : %s,\n" - " \"frequency\" : %zu\n", - contact->email.c_str(), - name.c_str(), - Mu::quote(contact->display_name()).c_str(), + mu_print_encoded( + " \"email\" : \"{}\",\n" + " \"name\" : {},\n" + " \"display\" : {},\n" + " \"last-seen\" : {},\n" + " \"last-seen-iso\" : \"{}\",\n" + " \"personal\" : {},\n" + " \"frequency\" : {}\n", + contact->email, + name, + Mu::quote(contact->display_name()), contact->message_date, - time_to_string("%FT%TZ", contact->message_date, true/*utc*/).c_str(), + time_to_string("%FT%TZ", contact->message_date, true/*utc*/), contact->personal ? "true" : "false", contact->frequency); - mu_print (" }}"); + mu_print(" }}"); } if (itype == ItemType::Footer) diff --git a/mu/mu-cmd-extract.cc b/mu/mu-cmd-extract.cc index b5b94cbb..98e85b4a 100644 --- a/mu/mu-cmd-extract.cc +++ b/mu/mu-cmd-extract.cc @@ -117,8 +117,7 @@ show_part(const MessagePart& part, size_t index, bool color) /* /\* disposition *\/ */ color_maybe(MU_COLOR_MAGENTA); - print_encoded(" [%s]", part.is_attachment() ? - "attachment" : "inline"); + mu_print_encoded(" [{}]", part.is_attachment() ? "attachment" : "inline"); /* size */ if (part.size() > 0) { color_maybe(MU_COLOR_CYAN); diff --git a/mu/mu-cmd-info.cc b/mu/mu-cmd-info.cc index 65d543f5..5fd46d80 100644 --- a/mu/mu-cmd-info.cc +++ b/mu/mu-cmd-info.cc @@ -98,13 +98,6 @@ topic_fields(const Options& opts) fields.add_row({"field-name", "alias", "short", "search", "value", "sexp", "example query", "description"}); - auto disp= [&](std::string_view sv)->std::string { - if (sv.empty()) - return ""; - else - return format("%.*s", STR_V(sv)); - }; - auto searchable=[&](const Field& field)->std::string { if (field.is_boolean_term()) return "boolean"; @@ -130,8 +123,8 @@ topic_fields(const Options& opts) searchable(field), field.is_value() ? "yes" : "no", field.include_in_sexp() ? "yes" : "no", - disp(field.example_query), - disp(field.description)}); + field.example_query, + field.description}); ++row; }); diff --git a/mu/mu-cmd-view.cc b/mu/mu-cmd-view.cc index d2bf9e0c..e2a2d704 100644 --- a/mu/mu-cmd-view.cc +++ b/mu/mu-cmd-view.cc @@ -106,7 +106,7 @@ body_or_summary(const Message& message, const Options& opts) const auto summ{summarize(body->c_str(), *opts.view.summary_len)}; print_field("Summary", summ, color); } else { - print_encoded("%s", body->c_str()); + mu_print_encoded("{}", *body); if (!g_str_has_suffix(body->c_str(), "\n")) mu_println(""); } diff --git a/mu/mu-options.cc b/mu/mu-options.cc index de2e368a..0f79b096 100644 --- a/mu/mu-options.cc +++ b/mu/mu-options.cc @@ -57,6 +57,53 @@ using namespace Mu; * helpers */ + + +/** + * array of associated pair elements -- like an alist + * but based on std::array and thus can be constexpr + */ +template + using AssocPairs = std::array, N>; + + +/** + * Get the first value of the pair where the second element is @param s. + * + * @param p AssocPairs + * @param s some second pair value + * + * @return the matching first pair value, or Nothing if not found. + */ +template +constexpr Option +to_first(const P& p, typename P::value_type::second_type s) +{ + for (const auto& item: p) + if (item.second == s) + return item.first; + return Nothing; +} + +/** + * Get the second value of the pair where the first element is @param f. + * + * @param p AssocPairs + * @param f some first pair value + * + * @return the matching second pair value, or Nothing if not found. + */ +template +constexpr Option +to_second(const P& p, typename P::value_type::first_type f) +{ + for (const auto& item: p) + if (item.first == f) + return item.second; + return Nothing; +} + + /** * Options-specific array-bases type that maps some enum to a pair */ @@ -316,7 +363,7 @@ sub_find(CLI::App& sub, Options& opts) smap.emplace(std::string(1, field.shortcut), field.id); if (!sopts.empty()) sopts += ", "; - sopts += format("%.*s|%c", STR_V(field.name), field.shortcut); + sopts += mu_format("{}|{}", field.name, field.shortcut); } }); sub.add_option("--sortfield,-s", opts.find.sortfield, @@ -326,7 +373,8 @@ sub_find(CLI::App& sub, Options& opts) ->default_val(Field::Id::Date) ->transform(CLI::CheckedTransformer(smap)); - sub.add_flag("--reverse,-z", opts.find.reverse, "Sort in descending order"); + sub.add_flag("--reverse,-z", opts.find.reverse, + "Sort in descending order"); sub.add_option("--bookmark,-b", opts.find.bookmark, "Use bookmarked query") @@ -347,7 +395,8 @@ sub_find(CLI::App& sub, Options& opts) "Command to execute on message file") ->type_name(""); - sub.add_option("query", opts.find.query, "Search query pattern(s)") + sub.add_option("query", opts.find.query, + "Search query pattern(s)") ->type_name(""); } @@ -792,12 +841,45 @@ test_ids() #ifdef BUILD_TESTS + + + + +enum struct TestEnum { A, B, C }; +constexpr AssocPairs +test_epairs = {{ + {TestEnum::A, "a"}, + {TestEnum::B, "b"}, + {TestEnum::C, "c"}, +}}; + +static constexpr Option +to_name(TestEnum te) +{ + return to_second(test_epairs, te); +} + +static constexpr Option +to_type(std::string_view name) +{ + return to_first(test_epairs, name); + +} + +static void +test_enum_pairs(void) +{ + assert_equal(to_name(TestEnum::A).value(), "a"); + g_assert_true(to_type("c").value() == TestEnum::C); +} + int main(int argc, char* argv[]) { mu_test_init(&argc, &argv); g_test_add_func("/options/ids", test_ids); + g_test_add_func("/option/enum-pairs", test_enum_pairs); return g_test_run(); }