/* ** Copyright (C) 2023-2026 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "config.h" #include "mu-cmd.hh" #include #include "utils/mu-utils.hh" #include #include #include #include using namespace Mu; using namespace tabulate; template <> struct fmt::formatter : ostream_formatter {}; static void colorify(Table& table, const Options& opts) { if (opts.nocolor || table.size() == 0) return; for (auto c = 0U; c != table.row(0).size(); ++c) { switch (c) { case 0: table.column(c).format() .font_color(Color::green) .font_style({FontStyle::bold}); break; case 1: table.column(c).format() .font_color(Color::blue); break; case 2: table.column(c).format() .font_color(Color::magenta); break; case 3: table.column(c).format() .font_color(Color::yellow); break; case 4: table.column(c).format() .font_color(Color::green); break; case 5: table.column(c).format() .font_color(Color::blue); break; case 6: table.column(c).format() .font_color(Color::magenta); break; case 7: table.column(c).format() .font_color(Color::yellow); break; default: table.column(c).format() .font_color(Color::grey); break; } } for (auto c = 0U; c != table.row(0).size(); ++c) table[0][c].format() .font_color(Color::white) .font_style({FontStyle::bold}); } static Result topic_fields(const Options& opts) { using namespace std::string_literals; Table fields; fields.add_row({"field-name", "alias", "short", "search", "value", "sexp", "example query", "description"}); auto searchable=[&](const Field& field)->std::string { if (field.is_boolean_term()) return "boolean"; if (field.is_phrasable_term()) return "phrase"; if (field.is_contact()) return "contact"; if (field.is_range()) return "range"; return "no"; }; size_t row{}; field_for_each([&](auto&& field){ if (field.is_internal()) return; // skip. fields.add_row({mu_format("{}", field.name), field.alias.empty() ? "" : mu_format("{}", field.alias), field.shortcut ? mu_format("{}", field.shortcut) : ""s, searchable(field), field.is_value() ? "yes" : "no", field.include_in_sexp() ? "yes" : "no", field.example_query, field.description}); ++row; }); colorify(fields, opts); std::cout << "# Message fields\n" << fields << '\n'; return Ok(); } static Result topic_combi_fields(const Options& opts) { using namespace std::string_literals; Table fields; fields.add_row({"combi-field", "fields"}); std::ranges::for_each(combi_fields(), [&](const auto& cfield) { std::string fnames; std::ranges::for_each(cfield.fields, [&](auto&& field) { if (!fnames.empty()) fnames += ", "; fnames += mu_format("{}", field.name); }); const std::string empty{""}; fields.add_row({cfield.name.empty() ? empty : mu_format("{}", cfield.name), fnames}); }); colorify(fields, opts); std::cout << "# Combination fields\n" << fields << '\n'; return Ok(); } static Result topic_flags(const Options& opts) { using namespace tabulate; using namespace std::string_literals; Table flags; flags.add_row({"flag", "shortcut", "category", "description"}); flag_infos_for_each([&](const MessageFlagInfo& info) { const auto catname = std::invoke( [](MessageFlagCategory cat)->std::string { switch(cat){ case MessageFlagCategory::Mailfile: return "file"; case MessageFlagCategory::Maildir: return "maildir"; case MessageFlagCategory::Content: return "content"; case MessageFlagCategory::Pseudo: return "pseudo"; default: return {}; } }, info.category); flags.add_row({mu_format("{}", info.name), mu_format("{}", info.shortcut), catname, std::string{info.description}}); }); colorify(flags, opts); std::cout << "# Message flags\n" << flags << '\n'; return Ok(); } static Result topic_store(const Mu::Store& store, const Options& opts) { Table info; const auto conf{store.config()}; info.add_row({"property", "value", "description"}); info.add_row({"database-path", store.path(), "Path to xapian database"}); info.add_row({"message-number", mu_format("{}", store.size()), "Number of messages in store"}); for (const auto& prop: Config::properties) { if (any_of(prop.flags & (Property::Flags::System|Property::Flags::Internal))) continue; switch(prop.id) { case Config::Id::PersonalAddresses: case Config::Id::IgnoredAddresses: { const auto addrs{conf.decode(conf.as_raw_string(prop))}; for (auto& addr: addrs) { info.add_row({std::string{prop.name}, addr, std::string{prop.description}}); } } break; default: info.add_row({std::string{prop.name},conf.as_display_string(prop), std::string{prop.description}}); } } colorify(info, opts); std::cout << info << '\n'; return Ok(); } static Result topic_maildirs(const Mu::Store& store, const Options& opts) { for (auto&& mdir: store.maildirs()) mu_println("{}", mdir); return Ok(); } static Result topic_mu(const Mu::Store& store, const Options& opts) { Table info; const auto conf{store.config()}; info.add_row({"property", "value", "description"}); for (const auto& prop: Config::properties) { if (any_of(prop.flags & Property::Flags::System)) { info.add_row({std::string{prop.name}, conf.as_display_string(prop), std::string{prop.description}}); } } colorify(info, opts); std::cout << info << '\n'; return Ok(); } Result Mu::mu_cmd_info(const Mu::Store& store, const Options& opts) { if (!locale_workaround()) return Err(Error::Code::User, "failed to find a working locale"); const auto topic{opts.info.topic}; if (topic == "store") return topic_store(store, opts); else if (topic == "maildirs") return topic_maildirs(store, opts); else if (topic == "fields") { if (auto&& res{topic_fields(opts)}; !res) return res; std::cout << std::endl; if (auto&& res{topic_combi_fields(opts)}; !res) return res; std::cout << std::endl; return topic_flags(opts); } else if (topic == "mu") { return topic_mu(store, opts); } else { if (auto&& res{topic_mu(store, opts)}; !res) return res; MaybeAnsi col{!opts.nocolor}; using Color = MaybeAnsi::Color; auto describe = [&](auto&& t, auto&& d)->std::string { return mu_format("{}{:<10}{} - {:>12}", col.fg(Color::Green), t, col.reset(), d); }; mu_println("\nother info topics ('mu info '):\n{}\n{}\n{}", describe("store", "information about the message store (database)"), describe("maildirs", "list the maildirs under the store's root-maildir"), describe("fields", "information about message fields")); } return Ok(); }