From 36f6e387aebee51e787ba1f0f7effceefa90cd1c Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Wed, 16 Nov 2022 22:51:15 +0200 Subject: [PATCH] mu: implement new command-line parser Implement a new command-line parser, based on CLI11. It's a bit more C++'ish, and allows for a lot of fancy things... some of which we have implemented here. Update the various commands to use the new Options struct Remove the old help strings; instead e.g. `mu help view` opens the manpage. Integrate the guile scripts more tightly. --- mu/meson.build | 15 +- mu/mu-cmd-cfind.cc | 91 ++--- mu/mu-cmd-extract.cc | 125 +++---- mu/mu-cmd-fields.cc | 21 +- mu/mu-cmd-find.cc | 296 +++++----------- mu/mu-cmd-index.cc | 30 +- mu/mu-cmd-server.cc | 13 +- mu/mu-cmd.cc | 272 ++++++-------- mu/mu-cmd.hh | 46 ++- mu/mu-config.cc | 738 -------------------------------------- mu/mu-config.hh | 246 ------------- mu/mu-help-strings.awk | 58 --- mu/mu-help-strings.txt | 200 ----------- mu/mu-options.cc | 780 +++++++++++++++++++++++++++++++++++++++++ mu/mu-options.hh | 278 +++++++++++++++ mu/mu.cc | 87 ++--- 16 files changed, 1439 insertions(+), 1857 deletions(-) delete mode 100644 mu/mu-config.cc delete mode 100644 mu/mu-config.hh delete mode 100644 mu/mu-help-strings.awk delete mode 100644 mu/mu-help-strings.txt create mode 100644 mu/mu-options.cc create mode 100644 mu/mu-options.hh diff --git a/mu/meson.build b/mu/meson.build index 99659ead..1c0d5d26 100644 --- a/mu/meson.build +++ b/mu/meson.build @@ -1,4 +1,4 @@ -## Copyright (C) 2021 Dirk-Jan C. Binnema +## Copyright (C) 2021-2022 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 @@ -14,15 +14,10 @@ ## along with this program; if not, write to the Free Software Foundation, ## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -awk_script=join_paths(meson.current_source_dir(), 'mu-help-strings.awk') -mu_help_strings_h=custom_target('mu_help', - input: 'mu-help-strings.txt', - output: 'mu-help-strings.inc', - command: [awk, '-f', awk_script, '@INPUT@'], - capture: true) mu = executable( 'mu', [ 'mu.cc', + 'mu-options.cc', 'mu-cmd-cfind.cc', 'mu-cmd-extract.cc', 'mu-cmd-fields.cc', @@ -30,11 +25,7 @@ mu = executable( 'mu-cmd-index.cc', 'mu-cmd-script.cc', 'mu-cmd-server.cc', - 'mu-cmd.cc', - 'mu-cmd.hh', - 'mu-config.cc', - 'mu-config.hh', - mu_help_strings_h + 'mu-cmd.cc' ], dependencies: [ glib_dep, gmime_dep, lib_mu_dep, thread_dep, config_h_dep ], cpp_args: ['-DMU_SCRIPTS_DIR="'+ join_paths(datadir, 'mu', 'scripts') + '"'], diff --git a/mu/mu-cmd-cfind.cc b/mu/mu-cmd-cfind.cc index 893ce84f..bd6fb2d9 100644 --- a/mu/mu-cmd-cfind.cc +++ b/mu/mu-cmd-cfind.cc @@ -27,7 +27,6 @@ #include "mu-cmd.hh" #include "mu-contacts-cache.hh" -#include "mu-runtime.hh" #include "utils/mu-util.h" #include "utils/mu-utils.hh" @@ -35,6 +34,7 @@ using namespace Mu; + /** * macro to check whether the string is empty, ie. if it's NULL or * it's length is 0 @@ -187,15 +187,17 @@ leave : { return nick; } +using Format = Options::Cfind::Format; + static void -print_header(const MuConfigFormat format) +print_header(Format format) { switch (format) { - case MU_CONFIG_FORMAT_BBDB: + case Format::Bbdb: g_print(";; -*-coding: utf-8-emacs;-*-\n" ";;; file-version: 6\n"); break; - case MU_CONFIG_FORMAT_MUTT_AB: + case Format::MuttAddressBook: g_print("Matching addresses in the mu database:\n"); break; default: @@ -275,7 +277,7 @@ print_plain(const std::string& email, const std::string& name, bool color) } struct ECData { - MuConfigFormat format; + Format format; gboolean color, personal; time_t after; GRegex* rx; @@ -304,28 +306,30 @@ each_contact(const Mu::Contact& ci, ECData& ecdata) ++ecdata.n; switch (ecdata.format) { - case MU_CONFIG_FORMAT_MUTT_ALIAS: + case Format::MuttAlias: each_contact_mutt_alias(ci.email, ci.name, ecdata.nicks); break; - case MU_CONFIG_FORMAT_MUTT_AB: + case Format::MuttAddressBook: mu_util_print_encoded("%s\t%s\t\n", ci.email.c_str(), ci.name.c_str()); break; - case MU_CONFIG_FORMAT_WL: each_contact_wl(ci.email, ci.name, ecdata.nicks); + case Format::Wanderlust: + each_contact_wl(ci.email, ci.name, ecdata.nicks); break; - case MU_CONFIG_FORMAT_ORG_CONTACT: + case Format::OrgContact: if (!ci.name.empty()) mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n", ci.name.c_str(), ci.email.c_str()); break; - case MU_CONFIG_FORMAT_BBDB: each_contact_bbdb(ci.email, ci.name, ci.message_date); + case Format::Bbdb: + each_contact_bbdb(ci.email, ci.name, ci.message_date); break; - case MU_CONFIG_FORMAT_CSV: + case Format::Csv: mu_util_print_encoded("%s,%s\n", ci.name.empty() ? "" : Mu::quote(ci.name).c_str(), Mu::quote(ci.email).c_str()); break; - case MU_CONFIG_FORMAT_DEBUG: { + case Format::Debug: { char datebuf[32]; const auto mdate(static_cast<::time_t>(ci.message_date)); ::strftime(datebuf, sizeof(datebuf), "%F %T", ::gmtime(&mdate)); @@ -346,21 +350,21 @@ each_contact(const Mu::Contact& ci, ECData& ecdata) static Result run_cmd_cfind(const Mu::Store& store, - const char* pattern, - gboolean personal, + const std::string& pattern, + bool personal, time_t after, - int maxnum, - const MuConfigFormat format, - gboolean color) + size_t maxnum, + Format format, + bool color) { ECData ecdata{}; GError *err{}; memset(&ecdata, 0, sizeof(ecdata)); - if (pattern) { + if (!pattern.empty()) { ecdata.rx = g_regex_new( - pattern, + pattern.c_str(), (GRegexCompileFlags)(G_REGEX_CASELESS | G_REGEX_OPTIMIZE), (GRegexMatchFlags)0, &err); @@ -393,47 +397,14 @@ run_cmd_cfind(const Mu::Store& store, return Ok(); } -static gboolean -cfind_params_valid(const MuConfig* opts) -{ - if (!opts || opts->cmd != MU_CONFIG_CMD_CFIND) - return FALSE; - - switch (opts->format) { - case MU_CONFIG_FORMAT_PLAIN: - case MU_CONFIG_FORMAT_MUTT_ALIAS: - case MU_CONFIG_FORMAT_MUTT_AB: - case MU_CONFIG_FORMAT_WL: - case MU_CONFIG_FORMAT_BBDB: - case MU_CONFIG_FORMAT_CSV: - case MU_CONFIG_FORMAT_ORG_CONTACT: - case MU_CONFIG_FORMAT_DEBUG: break; - default: - g_printerr("invalid output format %s\n", - opts->formatstr ? opts->formatstr : ""); - return FALSE; - } - - /* only one pattern allowed */ - if (opts->params[1] && opts->params[2]) { - g_printerr("usage: mu cfind [options] []\n"); - return FALSE; - } - - return TRUE; -} - Result -Mu::mu_cmd_cfind(const Mu::Store& store, const MuConfig* opts) +Mu::mu_cmd_cfind(const Mu::Store& store, const Mu::Options& opts) { - if (!cfind_params_valid(opts)) - return Err(Error::Code::InvalidArgument, "error in parameters"); - else - return run_cmd_cfind(store, - opts->params[1], - opts->personal, - opts->after, - opts->maxnum, - opts->format, - !opts->nocolor); + return run_cmd_cfind(store, + opts.cfind.rx_pattern, + opts.cfind.personal, + opts.cfind.after.value_or(0), + opts.cfind.maxnum.value_or(0), + opts.cfind.format, + !opts.nocolor); } diff --git a/mu/mu-cmd-extract.cc b/mu/mu-cmd-extract.cc index d071441c..dd4fd872 100644 --- a/mu/mu-cmd-extract.cc +++ b/mu/mu-cmd-extract.cc @@ -19,7 +19,6 @@ #include "config.h" #include "mu-cmd.hh" -#include "mu-config.hh" #include "utils/mu-util.h" #include "utils/mu-utils.hh" #include @@ -29,19 +28,19 @@ using namespace Mu; static Result -save_part(const Message::Part& part, size_t idx, const MuConfig* opts) +save_part(const Message::Part& part, size_t idx, const Options& opts) { const auto targetdir = std::invoke([&]{ - auto tdir{std::string{opts->targetdir ? opts->targetdir : ""}}; + const auto tdir{opts.extract.targetdir}; return tdir.empty() ? tdir : tdir + G_DIR_SEPARATOR_S; }); const auto path{targetdir + part.cooked_filename().value_or(format("part-%zu", idx))}; - if (auto&& res{part.to_file(path, opts->overwrite)}; !res) + if (auto&& res{part.to_file(path, opts.extract.overwrite)}; !res) return Err(res.error()); - if (opts->play) { + if (opts.extract.play) { GError *err{}; if (auto res{mu_util_play(path.c_str(), &err)}; res != MU_OK) @@ -53,39 +52,37 @@ save_part(const Message::Part& part, size_t idx, const MuConfig* opts) } static Result -save_parts(const std::string& path, Option& filename_rx, - const MuConfig* opts) +save_parts(const std::string& path, const std::string& filename_rx, + const Options& opts) { - auto message{Message::make_from_path(path, mu_config_message_options(opts))}; + auto message{Message::make_from_path(path, message_options(opts.extract))}; if (!message) return Err(std::move(message.error())); - size_t partnum{}, saved_num{}; - const auto partnums = std::invoke([&]()->std::vector { - std::vector nums; - for (auto&& numstr : split(opts->parts ? opts->parts : "", ',')) - nums.emplace_back( - static_cast(::atoi(numstr.c_str()))); - return nums; + for (auto&& part: message->parts()) { + ++partnum; + // should we extract this part? + const auto do_extract = std::invoke([&]() { + + if (opts.extract.save_all) + return true; + else if (opts.extract.save_attachments && + part.looks_like_attachment()) + return true; + else if (seq_some(opts.extract.parts, + [&](auto&& num){return num==partnum;})) + return true; + else if (!filename_rx.empty() && part.raw_filename() && + std::regex_match(*part.raw_filename(), + std::regex{filename_rx})) + return true; + else + return false; }); - - for (auto&& part: message->parts()) { - - ++partnum; - - if (!opts->save_all) { - - if (!partnums.empty() && - !seq_some(partnums, [&](auto&& num){return num==partnum;})) - continue; // not a wanted partnum. - - if (filename_rx && (!part.raw_filename() || - !std::regex_match(*part.raw_filename(), - std::regex{*filename_rx}))) - continue; // not a wanted pattern. - } + if (!do_extract) + continue; if (auto res = save_part(part, partnum, opts); !res) return res; @@ -93,11 +90,11 @@ save_parts(const std::string& path, Option& filename_rx, ++saved_num; } - // if (saved_num == 0) - // return Err(Error::Code::File, - // "no %s extracted from this message", - // opts->save_attachments ? "attachments" : "parts"); - // else + if (saved_num == 0) + return Err(Error::Code::File, + "no %s extracted from this message", + opts.extract.save_attachments ? "attachments" : "parts"); + else return Ok(); } @@ -140,66 +137,32 @@ show_part(const MessagePart& part, size_t index, bool color) } static Mu::Result -show_parts(const char* path, const MuConfig* opts) +show_parts(const std::string& path, const Options& opts) { - //msgopts = mu_config_get_msg_options(opts); - - auto msg_res{Message::make_from_path(path, mu_config_message_options(opts))}; + auto msg_res{Message::make_from_path(path, message_options(opts.extract))}; if (!msg_res) return Err(std::move(msg_res.error())); - /* TODO: update this for crypto */ size_t index{}; g_print("MIME-parts in this message:\n"); for (auto&& part: msg_res->parts()) - show_part(part, ++index, !opts->nocolor); + show_part(part, ++index, !opts.nocolor); return Ok(); } -static Mu::Result -check_params(const MuConfig* opts) -{ - size_t param_num; - param_num = mu_config_param_num(opts); - - if (param_num < 2) - return Err(Error::Code::InvalidArgument, "parameters missing"); - - if (opts->save_attachments || opts->save_all) - if (opts->parts || param_num == 3) - return Err(Error::Code::User, - "--save-attachments and --save-all don't " - "accept a filename pattern or --parts"); - - if (opts->save_attachments && opts->save_all) - return Err(Error::Code::User, - "only one of --save-attachments and" - " --save-all is allowed"); - return Ok(); -} - - Mu::Result -Mu::mu_cmd_extract(const MuConfig* opts) +Mu::mu_cmd_extract(const Options& opts) { - if (!opts || opts->cmd != MU_CONFIG_CMD_EXTRACT) - return Err(Error::Code::Internal, "error in arguments"); - if (auto res = check_params(opts); !res) - return Err(std::move(res.error())); + if (opts.extract.parts.empty() && + !opts.extract.save_attachments && !opts.extract.save_all && + opts.extract.filename_rx.empty()) + return show_parts(opts.extract.message, opts); /* show, don't save */ - if (!opts->params[2] && !opts->parts && - !opts->save_attachments && !opts->save_all) - return show_parts(opts->params[1], opts); /* show, don't save */ - - if (!mu_util_check_dir(opts->targetdir, FALSE, TRUE)) + if (!mu_util_check_dir(opts.extract.targetdir.c_str(), FALSE, TRUE)) return Err(Error::Code::File, "target '%s' is not a writable directory", - opts->targetdir); + opts.extract.targetdir.c_str()); - Option pattern{}; - if (opts->params[2]) - pattern = opts->params[2]; - - return save_parts(opts->params[1], pattern, opts); + return save_parts(opts.extract.message, opts.extract.filename_rx, opts); } diff --git a/mu/mu-cmd-fields.cc b/mu/mu-cmd-fields.cc index 729ccf44..f92ff1fe 100644 --- a/mu/mu-cmd-fields.cc +++ b/mu/mu-cmd-fields.cc @@ -30,9 +30,9 @@ using namespace tabulate; static void -table_header(Table& table, const MuConfig* opts) +table_header(Table& table, const Options& opts) { - if (opts->nocolor) + if (opts.nocolor) return; (*table.begin()).format() @@ -42,7 +42,7 @@ table_header(Table& table, const MuConfig* opts) } static void -show_fields(const MuConfig* opts) +show_fields(const Options& opts) { using namespace std::string_literals; @@ -54,7 +54,7 @@ show_fields(const MuConfig* opts) if (sv.empty()) return ""; else - return format("%*s", STR_V(sv)); + return format("%.*s", STR_V(sv)); }; auto searchable=[&](const Field& field)->std::string { @@ -76,8 +76,8 @@ show_fields(const MuConfig* opts) if (field.is_internal()) return; // skip. - fields.add_row({format("%*s", STR_V(field.name)), - field.alias.empty() ? "" : format("%*s", STR_V(field.alias)), + fields.add_row({format("%.*s", STR_V(field.name)), + field.alias.empty() ? "" : format("%.*s", STR_V(field.alias)), field.shortcut ? format("%c", field.shortcut) : ""s, searchable(field), field.is_value() ? "yes" : "no", @@ -93,7 +93,7 @@ show_fields(const MuConfig* opts) } static void -show_flags(const MuConfig* opts) +show_flags(const Options& opts) { using namespace tabulate; using namespace std::string_literals; @@ -119,7 +119,7 @@ show_flags(const MuConfig* opts) } }, info.category); - flags.add_row({format("%*s", STR_V(info.name)), + flags.add_row({format("%.*s", STR_V(info.name)), format("%c", info.shortcut), catname, std::string{info.description}}); @@ -133,14 +133,11 @@ show_flags(const MuConfig* opts) Result -Mu::mu_cmd_fields(const MuConfig* opts) +Mu::mu_cmd_fields(const Options& opts) { - g_return_val_if_fail(opts, Err(Error::Code::Internal, "no opts")); - if (!locale_workaround()) return Err(Error::Code::User, "failed to find a working locale"); - std::cout << "#\n# message fields\n#\n"; show_fields(opts); std::cout << "\n#\n# message flags\n#\n"; diff --git a/mu/mu-cmd-find.cc b/mu/mu-cmd-find.cc index 5eee98f9..1534ec16 100644 --- a/mu/mu-cmd-find.cc +++ b/mu/mu-cmd-find.cc @@ -1,4 +1,4 @@ -/* + /* ** Copyright (C) 2008-2022 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it @@ -33,7 +33,6 @@ #include "mu-query-match-deciders.hh" #include "mu-query.hh" #include "mu-bookmarks.hh" -#include "mu-runtime.hh" #include "message/mu-message.hh" #include "utils/mu-option.hh" @@ -44,6 +43,8 @@ using namespace Mu; +using Format = Options::Find::Format; + struct OutputInfo { Xapian::docid docid{}; bool header{}; @@ -56,52 +57,50 @@ constexpr auto FirstOutput{OutputInfo{0, true, false, {}, {}}}; constexpr auto LastOutput{OutputInfo{0, false, true, {}, {}}}; using OutputFunc = std::function& msg, const OutputInfo&, - const MuConfig*, GError**)>; + const Options&, GError**)>; + +using Format = Options::Find::Format; static Result print_internal(const Store& store, const std::string& expr, - gboolean xapian, - gboolean warn) + bool xapian, + bool warn) { std::cout << store.parse_query(expr, xapian) << "\n"; return Ok(); } static Result -run_query(const Store& store, const std::string& expr, const MuConfig* opts) +run_query(const Store& store, const std::string& expr, const Options& opts) { - const auto sortfield{field_from_name(opts->sortfield ? opts->sortfield : "")}; - if (!sortfield && opts->sortfield) - return Err(Error::Code::InvalidArgument, - "invalid sort field: '%s'", opts->sortfield); - Mu::QueryFlags qflags{QueryFlags::SkipUnreadable}; - if (opts->reverse) + if (opts.find.reverse) qflags |= QueryFlags::Descending; - if (opts->skip_dups) + if (opts.find.skip_dups) qflags |= QueryFlags::SkipDuplicates; - if (opts->include_related) + if (opts.find.include_related) qflags |= QueryFlags::IncludeRelated; - if (opts->threads) + if (opts.find.threads) qflags |= QueryFlags::Threading; - return store.run_query(expr, sortfield.value_or(field_from_id(Field::Id::Date)).id, - qflags, opts->maxnum); + return store.run_query(expr, + opts.find.sortfield, + qflags, opts.find.maxnum.value_or(0)); } -static gboolean -exec_cmd(const Option& msg, const OutputInfo& info, const MuConfig* opts, GError** err) +static bool +exec_cmd(const Option& msg, const OutputInfo& info, const Options& opts, GError** err) { if (!msg) - return TRUE; + return true; gint status; char * cmdline, *escpath; - gboolean rv; + bool rv; escpath = g_shell_quote(msg->path().c_str()); - cmdline = g_strdup_printf("%s %s", opts->exec, escpath); + cmdline = g_strdup_printf("%s %s", opts.find.exec.c_str(), escpath); rv = g_spawn_command_line_sync(cmdline, NULL, NULL, &status, err); @@ -111,82 +110,60 @@ exec_cmd(const Option& msg, const OutputInfo& info, const MuConfig* opt return rv; } -static gchar* -resolve_bookmark(const MuConfig* opts, GError** err) +static Result +resolve_bookmark(const Options& opts) { - MuBookmarks* bm; - char* val; - const gchar* bmfile; + const auto bmfile = opts.runtime_path(RuntimePath::Bookmarks); + auto bm = mu_bookmarks_new(bmfile.c_str()); + if (!bm) + return Err(Error::Code::File, + "failed to open bookmarks file '%s'", bmfile.c_str()); - bmfile = mu_runtime_path(MU_RUNTIME_PATH_BOOKMARKS); - bm = mu_bookmarks_new(bmfile); - if (!bm) { - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_FILE_CANNOT_OPEN, - "failed to open bookmarks file '%s'", - bmfile); - return FALSE; + const auto bookmark{opts.find.bookmark}; + const auto val = mu_bookmarks_lookup(bm, bookmark.c_str()); + if (!val) { + mu_bookmarks_destroy(bm); + return Err(Error::Code::NoMatches, + "bookmark '%s' not found", bookmark.c_str()); } - val = (gchar*)mu_bookmarks_lookup(bm, opts->bookmark); - if (!val) - g_set_error(err, - MU_ERROR_DOMAIN, - MU_ERROR_NO_MATCHES, - "bookmark '%s' not found", - opts->bookmark); - else - val = g_strdup(val); - mu_bookmarks_destroy(bm); - return val; + return Ok(std::string(val)); } static Result -get_query(const MuConfig* opts) +get_query(const Options& opts) { - GError *err{}; - gchar *query, *bookmarkval; + if (opts.find.bookmark.empty() && opts.find.query.empty()) + return Err(Error::Code::InvalidArgument, + "neither bookmark nor query"); - /* params[0] is 'find', actual search params start with [1] */ - if (!opts->bookmark && !opts->params[1]) - return Err(Error::Code::InvalidArgument, "error in parameters"); - - bookmarkval = {}; - if (opts->bookmark) { - bookmarkval = resolve_bookmark(opts, &err); - if (!bookmarkval) - return Err(Error::Code::Command, &err, - "failed to resolve bookmark"); + std::string bookmark; + if (!opts.find.bookmark.empty()) { + const auto res = resolve_bookmark(opts); + if (!res) + return Err(std::move(res.error())); + bookmark = res.value() + " "; } - query = g_strjoinv(" ", &opts->params[1]); - if (bookmarkval) { - gchar* tmp; - tmp = g_strdup_printf("%s %s", bookmarkval, query); - g_free(query); - query = tmp; - } - g_free(bookmarkval); - - return Ok(to_string_gchar(std::move(query))); + auto&& query{join(opts.find.query, " ")}; + return Ok(bookmark + query); } static bool -prepare_links(const MuConfig* opts, GError** err) +prepare_links(const Options& opts, GError** err) { /* note, mu_maildir_mkdir simply ignores whatever part of the * mail dir already exists */ - if (auto&& res = maildir_mkdir(opts->linksdir, 0700, true); !res) { + if (auto&& res = maildir_mkdir(opts.find.linksdir, 0700, true); !res) { res.error().fill_g_error(err); return false; } - if (!opts->clearlinks) + if (!opts.find.clearlinks) return false; - if (auto&& res = maildir_clear_links(opts->linksdir); !res) { + if (auto&& res = maildir_clear_links(opts.find.linksdir); !res) { res.error().fill_g_error(err); return false; } @@ -195,14 +172,14 @@ prepare_links(const MuConfig* opts, GError** err) } static bool -output_link(const Option& msg, const OutputInfo& info, const MuConfig* opts, GError** err) +output_link(const Option& msg, const OutputInfo& info, const Options& opts, GError** err) { if (info.header) return prepare_links(opts, err); else if (info.footer) return true; - if (auto&& res = maildir_link(msg->path(), opts->linksdir); !res) { + if (auto&& res = maildir_link(msg->path(), opts.find.linksdir); !res) { res.error().fill_g_error(err); return false; } @@ -211,7 +188,7 @@ output_link(const Option& msg, const OutputInfo& info, const MuConfig* } static void -ansi_color_maybe(Field::Id field_id, gboolean color) +ansi_color_maybe(Field::Id field_id, bool color) { const char* ansi; @@ -238,7 +215,7 @@ ansi_color_maybe(Field::Id field_id, gboolean color) } static void -ansi_reset_maybe(Field::Id field_id, gboolean color) +ansi_reset_maybe(Field::Id field_id, bool color) { if (!color) return; /* nothing to do */ @@ -275,7 +252,7 @@ display_field(const Message& msg, Field::Id field_id) } static void -print_summary(const Message& msg, const MuConfig* opts) +print_summary(const Message& msg, const Options& opts) { const auto body{msg.body_text()}; if (!body) @@ -283,7 +260,7 @@ print_summary(const Message& msg, const MuConfig* opts) const auto summ{to_string_opt_gchar( mu_str_summarize(body->c_str(), - opts->summary_len))}; + opts.find.summary_len.value_or(0)))}; g_print("Summary: "); mu_util_fputs_encoded(summ ? summ->c_str() : "", stdout); @@ -291,7 +268,7 @@ print_summary(const Message& msg, const MuConfig* opts) } static void -thread_indent(const QueryMatch& info, const MuConfig* opts) +thread_indent(const QueryMatch& info, const Options& opts) { const auto is_root{any_of(info.flags & QueryMatch::Flags::Root)}; const auto first_child{any_of(info.flags & QueryMatch::Flags::First)}; @@ -301,7 +278,7 @@ thread_indent(const QueryMatch& info, const MuConfig* opts) // const auto is_related{any_of(info.flags & QueryMatch::Flags::Related)}; /* indent */ - if (opts->debug) { + if (opts.debug) { ::fputs(info.thread_path.c_str(), stdout); ::fputs(" ", stdout); } else @@ -322,18 +299,15 @@ thread_indent(const QueryMatch& info, const MuConfig* opts) } static void -output_plain_fields(const Message& msg, const char* fields, - gboolean color, gboolean threads) +output_plain_fields(const Message& msg, const std::string& fields, + bool color, bool threads) { - const char* myfields; - int nonempty; + size_t nonempty{}; - g_return_if_fail(fields); - - for (myfields = fields, nonempty = 0; *myfields; ++myfields) { - const auto field_opt{field_from_shortcut(*myfields)}; + for (auto&& k: fields) { + const auto field_opt{field_from_shortcut(k)}; if (!field_opt || (!field_opt->is_value() && !field_opt->is_contact())) - nonempty += printf("%c", *myfields); + nonempty += printf("%c", k); else { ansi_color_maybe(field_opt->id, color); @@ -347,29 +321,29 @@ output_plain_fields(const Message& msg, const char* fields, fputs("\n", stdout); } -static gboolean +static bool output_plain(const Option& msg, const OutputInfo& info, - const MuConfig* opts, GError** err) + const Options& opts, GError** err) { if (!msg) return true; /* we reuse the color (whatever that may be) * for message-priority for threads, too */ - ansi_color_maybe(Field::Id::Priority, !opts->nocolor); - if (opts->threads && info.match_info) + ansi_color_maybe(Field::Id::Priority, !opts.nocolor); + if (opts.find.threads && info.match_info) thread_indent(*info.match_info, opts); - output_plain_fields(*msg, opts->fields, !opts->nocolor, opts->threads); + output_plain_fields(*msg, opts.find.fields, !opts.nocolor, opts.find.threads); - if (opts->summary_len > 0) + if (opts.view.summary_len) print_summary(*msg, opts); - return TRUE; + return true; } static bool -output_sexp(const Option& msg, const OutputInfo& info, const MuConfig* opts, GError** err) +output_sexp(const Option& msg, const OutputInfo& info, const Options& opts, GError** err) { if (msg) { if (const auto sexp{msg->sexp()}; !sexp.empty()) @@ -383,7 +357,7 @@ output_sexp(const Option& msg, const OutputInfo& info, const MuConfig* } static bool -output_json(const Option& msg, const OutputInfo& info, const MuConfig* opts, GError** err) +output_json(const Option& msg, const OutputInfo& info, const Options& opts, GError** err) { if (info.header) { g_print("[\n"); @@ -416,7 +390,7 @@ print_attr_xml(const std::string& elm, const std::string& str) } static bool -output_xml(const Option& msg, const OutputInfo& info, const MuConfig* opts, GError** err) +output_xml(const Option& msg, const OutputInfo& info, const Options& opts, GError** err) { if (info.header) { g_print("\n"); @@ -445,29 +419,32 @@ output_xml(const Option& msg, const OutputInfo& info, const MuConfig* o } static OutputFunc -get_output_func(const MuConfig* opts, GError** err) +get_output_func(const Options& opts, GError** err) { - switch (opts->format) { - case MU_CONFIG_FORMAT_LINKS: return output_link; - case MU_CONFIG_FORMAT_EXEC: return exec_cmd; - case MU_CONFIG_FORMAT_PLAIN: return output_plain; - case MU_CONFIG_FORMAT_XML: return output_xml; - case MU_CONFIG_FORMAT_SEXP: return output_sexp; - case MU_CONFIG_FORMAT_JSON: return output_json; + if (!opts.find.exec.empty()) + return exec_cmd; + switch (opts.find.format) { + case Format::Links: return output_link; + case Format::Plain: return output_plain; + case Format::Xml: return output_xml; + case Format::Sexp: return output_sexp; + case Format::Json: return output_json; default: g_return_val_if_reached(NULL); return NULL; } + + } static Result -output_query_results(const QueryResults& qres, const MuConfig* opts) +output_query_results(const QueryResults& qres, const Options& opts) { GError* err{}; const auto output_func{get_output_func(opts, &err)}; if (!output_func) return Err(Error::Code::Query, &err, "failed to find output function"); - gboolean rv{true}; + bool rv{true}; output_func(Nothing, FirstOutput, opts, {}); size_t n{0}; @@ -477,7 +454,7 @@ output_query_results(const QueryResults& qres, const MuConfig* opts) if (!msg) continue; - if (opts->after != 0 && msg->changed() < opts->after) + if (msg->changed() < opts.find.after.value_or(0)) continue; rv = output_func(msg, @@ -501,7 +478,7 @@ output_query_results(const QueryResults& qres, const MuConfig* opts) } static Result -process_query(const Store& store, const std::string& expr, const MuConfig* opts) +process_query(const Store& store, const std::string& expr, const Options& opts) { auto qres{run_query(store, expr, opts)}; if (!qres) @@ -513,98 +490,17 @@ process_query(const Store& store, const std::string& expr, const MuConfig* opts) return output_query_results(*qres, opts); } -static Result -execute_find(const Store& store, const MuConfig* opts) +Result +Mu::mu_cmd_find(const Store& store, const Options& opts) { auto expr{get_query(opts)}; if (!expr) return Err(expr.error()); - if (opts->format == MU_CONFIG_FORMAT_XQUERY) - return print_internal(store, *expr, TRUE, FALSE); - else if (opts->format == MU_CONFIG_FORMAT_MQUERY) - return print_internal(store, *expr, FALSE, opts->verbose); + if (opts.find.format == Format::XQuery) + return print_internal(store, *expr, true, false); + else if (opts.find.format == Format::MQuery) + return print_internal(store, *expr, false, opts.verbose); else return process_query(store, *expr, opts); } - -static gboolean -format_params_valid(const MuConfig* opts, GError** err) -{ - switch (opts->format) { - case MU_CONFIG_FORMAT_EXEC: break; - case MU_CONFIG_FORMAT_PLAIN: - case MU_CONFIG_FORMAT_SEXP: - case MU_CONFIG_FORMAT_JSON: - case MU_CONFIG_FORMAT_LINKS: - case MU_CONFIG_FORMAT_XML: - case MU_CONFIG_FORMAT_XQUERY: - case MU_CONFIG_FORMAT_MQUERY: - if (opts->exec) { - mu_util_g_set_error(err, - MU_ERROR_IN_PARAMETERS, - "--exec and --format cannot be combined"); - return FALSE; - } - break; - default: - mu_util_g_set_error(err, - MU_ERROR_IN_PARAMETERS, - "invalid output format %s", - opts->formatstr ? opts->formatstr : ""); - return FALSE; - } - - if (opts->format == MU_CONFIG_FORMAT_LINKS && !opts->linksdir) { - mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, "missing --linksdir argument"); - return FALSE; - } - - if (opts->linksdir && opts->format != MU_CONFIG_FORMAT_LINKS) { - mu_util_g_set_error(err, - MU_ERROR_IN_PARAMETERS, - "--linksdir is only valid with --format=links"); - return FALSE; - } - - return TRUE; -} - -static gboolean -query_params_valid(const MuConfig* opts, GError** err) -{ - const gchar* xpath; - - if (!opts->params[1]) { - mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, "missing query"); - return FALSE; - } - - xpath = mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB); - if (mu_util_check_dir(xpath, TRUE, FALSE)) - return TRUE; - - mu_util_g_set_error(err, - MU_ERROR_FILE_CANNOT_READ, - "'%s' is not a readable Xapian directory", - xpath); - return FALSE; -} - -Result -Mu::mu_cmd_find(const Store& store, const MuConfig* opts) -{ - g_return_val_if_fail(opts, Err(Error::Code::Internal, "no opts")); - g_return_val_if_fail(opts->cmd == MU_CONFIG_CMD_FIND, Err(Error::Code::Internal, - "wrong command")); - MuConfig myopts{*opts}; - - if (myopts.exec) - myopts.format = MU_CONFIG_FORMAT_EXEC; /* pseudo format */ - - GError *err{}; - if (!query_params_valid(&myopts, &err) || !format_params_valid(&myopts, &err)) - return Err(Error::Code::InvalidArgument, &err, "invalid argument"); - else - return execute_find(store, &myopts); -} diff --git a/mu/mu-cmd-index.cc b/mu/mu-cmd-index.cc index 1559cd66..269dc725 100644 --- a/mu/mu-cmd-index.cc +++ b/mu/mu-cmd-index.cc @@ -32,7 +32,6 @@ #include "index/mu-indexer.hh" #include "mu-store.hh" -#include "mu-runtime.hh" #include "utils/mu-util.h" @@ -78,24 +77,17 @@ print_stats(const Indexer::Progress& stats, bool color) } Result -Mu::mu_cmd_index(Mu::Store& store, const MuConfig* opts) +Mu::mu_cmd_index(Store& store, const Options& opts) { - if (!opts || opts->cmd != MU_CONFIG_CMD_INDEX || opts->params[1]) - return Err(Error::Code::InvalidArgument, "error in parameters"); - - if (opts->max_msg_size < 0) - return Err(Error::Code::InvalidArgument, - "the maximum message size must be >= 0"); - const auto mdir{store.properties().root_maildir}; if (G_UNLIKELY(access(mdir.c_str(), R_OK) != 0)) return Err(Error::Code::File, "'%s' is not readable: %s", mdir.c_str(), g_strerror(errno)); - MaybeAnsi col{!opts->nocolor}; + MaybeAnsi col{!opts.nocolor}; using Color = MaybeAnsi::Color; - if (!opts->quiet) { - if (opts->lazycheck) + if (!opts.quiet) { + if (opts.index.lazycheck) std::cout << "lazily "; std::cout << "indexing maildir " << col.fg(Color::Green) @@ -105,8 +97,8 @@ Mu::mu_cmd_index(Mu::Store& store, const MuConfig* opts) } Mu::Indexer::Config conf{}; - conf.cleanup = !opts->nocleanup; - conf.lazy_check = opts->lazycheck; + conf.cleanup = !opts.index.nocleanup; + conf.lazy_check = opts.index.lazycheck; // ignore .noupdate with an empty store. conf.ignore_noupdate = store.empty(); @@ -115,12 +107,12 @@ Mu::mu_cmd_index(Mu::Store& store, const MuConfig* opts) auto& indexer{store.indexer()}; indexer.start(conf); while (!caught_signal && indexer.is_running()) { - if (!opts->quiet) - print_stats(indexer.progress(), !opts->nocolor); + if (!opts.quiet) + print_stats(indexer.progress(), !opts.nocolor); std::this_thread::sleep_for(std::chrono::milliseconds(250)); - if (!opts->quiet) { + if (!opts.quiet) { std::cout << "\r"; std::cout.flush(); } @@ -128,8 +120,8 @@ Mu::mu_cmd_index(Mu::Store& store, const MuConfig* opts) store.indexer().stop(); - if (!opts->quiet) { - print_stats(store.indexer().progress(), !opts->nocolor); + if (!opts.quiet) { + print_stats(store.indexer().progress(), !opts.nocolor); std::cout << std::endl; } diff --git a/mu/mu-cmd-server.cc b/mu/mu-cmd-server.cc index 7028ee4e..a6d88a06 100644 --- a/mu/mu-cmd-server.cc +++ b/mu/mu-cmd-server.cc @@ -26,7 +26,6 @@ #include -#include "mu-runtime.hh" #include "mu-cmd.hh" #include "mu-server.hh" @@ -111,9 +110,9 @@ report_error(const Mu::Error& err) noexcept Result -Mu::mu_cmd_server(const MuConfig* opts) try { +Mu::mu_cmd_server(const Mu::Options& opts) try { - auto store = Store::make(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), + auto store = Store::make(opts.runtime_path(RuntimePath::XapianDb), Store::Options::Writable); if (!store) return Err(store.error()); @@ -123,20 +122,18 @@ Mu::mu_cmd_server(const MuConfig* opts) try { "readline: %s", store->properties().database_path.c_str(), store->properties().root_maildir.c_str(), - opts->debug ? "yes" : "no", + opts.debug ? "yes" : "no", have_readline() ? "yes" : "no"); tty = ::isatty(::fileno(stdout)); - const auto eval = std::string{opts->commands ? "(help :full t)" - : opts->eval ? opts->eval - : ""}; + const auto eval = std::string{opts.server.commands ? "(help :full t)" : opts.server.eval}; if (!eval.empty()) { server.invoke(eval); return Ok(); } // Note, the readline stuff is inactive unless on a tty. - const auto histpath{std::string{mu_runtime_path(MU_RUNTIME_PATH_CACHE)} + "/history"}; + const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"}; setup_readline(histpath, 50); install_sig_handler(); diff --git a/mu/mu-cmd.cc b/mu/mu-cmd.cc index 1b6bc36a..304bfca5 100644 --- a/mu/mu-cmd.cc +++ b/mu/mu-cmd.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2010-2020 Dirk-Jan C. Binnema +** Copyright (C) 2010-2022 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 @@ -28,11 +28,10 @@ #include #include -#include "mu-config.hh" +#include "mu-options.hh" #include "mu-cmd.hh" #include "mu-maildir.hh" #include "mu-contacts-cache.hh" -#include "mu-runtime.hh" #include "message/mu-message.hh" #include "message/mu-mime-object.hh" @@ -49,7 +48,7 @@ using namespace Mu; static Mu::Result -view_msg_sexp(const Message& message, const MuConfig* opts) +view_msg_sexp(const Message& message, const Options& opts) { ::fputs(message.sexp().to_string().c_str(), stdout); ::fputs("\n", stdout); @@ -59,7 +58,7 @@ view_msg_sexp(const Message& message, const MuConfig* opts) static std::string /* return comma-sep'd list of attachments */ -get_attach_str(const Message& message, const MuConfig* opts) +get_attach_str(const Message& message, const Options& opts) { std::string str; seq_for_each(message.parts(), [&](auto&& part) { @@ -100,12 +99,11 @@ print_field(const std::string& field, const std::string& val, bool color) /* a summary_len of 0 mean 'don't show summary, show body */ static void -body_or_summary(const Message& message, const MuConfig* opts) +body_or_summary(const Message& message, const Options& opts) { gboolean color; - //int my_opts = mu_config_get_msg_options(opts) | MU_MSG_OPTION_CONSOLE_PASSWORD; - color = !opts->nocolor; + color = !opts.nocolor; const auto body{message.body_text()}; if (!body || body->empty()) { @@ -121,9 +119,9 @@ body_or_summary(const Message& message, const MuConfig* opts) return; } - if (opts->summary_len != 0) { + if (opts.view.summary_len) { gchar* summ; - summ = mu_str_summarize(body->c_str(), opts->summary_len); + summ = mu_str_summarize(body->c_str(), *opts.view.summary_len); print_field("Summary", summ, color); g_free(summ); } else { @@ -136,9 +134,9 @@ body_or_summary(const Message& message, const MuConfig* opts) /* we ignore fields for now */ /* summary_len == 0 means "no summary */ static Mu::Result -view_msg_plain(const Message& message, const MuConfig* opts) +view_msg_plain(const Message& message, const Options& opts) { - const auto color{!opts->nocolor}; + const auto color{!opts.nocolor}; print_field("From", to_string(message.from()), color); print_field("To", to_string(message.to()), color); @@ -158,16 +156,18 @@ view_msg_plain(const Message& message, const MuConfig* opts) } static Mu::Result -handle_msg(const std::string& fname, const MuConfig* opts) +handle_msg(const std::string& fname, const Options& opts) { - auto message{Message::make_from_path(fname, mu_config_message_options(opts))}; + using Format = Options::View::Format; + + auto message{Message::make_from_path(fname, message_options(opts.view))}; if (!message) return Err(message.error()); - switch (opts->format) { - case MU_CONFIG_FORMAT_PLAIN: + switch (opts.view.format) { + case Format::Plain: return view_msg_plain(*message, opts); - case MU_CONFIG_FORMAT_SEXP: + case Format::Sexp: return view_msg_sexp(*message, opts); default: g_critical("bug: should not be reached"); @@ -176,35 +176,13 @@ handle_msg(const std::string& fname, const MuConfig* opts) } static Mu::Result -view_params_valid(const MuConfig* opts) +cmd_view(const Options& opts) { - /* note: params[0] will be 'view' */ - if (!opts->params[0] || !opts->params[1]) - return Err(Error::Code::InvalidArgument, "error in parameters"); - - switch (opts->format) { - case MU_CONFIG_FORMAT_PLAIN: - case MU_CONFIG_FORMAT_SEXP: break; - default: - return Err(Error::Code::InvalidArgument, "invalid output format"); - } - - return Ok(); -} - -static Mu::Result -cmd_view(const MuConfig* opts) -{ - if (!opts || opts->cmd != Mu::MU_CONFIG_CMD_VIEW) - return Err(Error::Code::InvalidArgument, "invalid parameters"); - if (auto res = view_params_valid(opts); !res) - return res; - - for (auto i = 1; opts->params[i]; ++i) { - if (auto res = handle_msg(opts->params[i], opts); !res) + for (auto&& file: opts.view.files) { + if (auto res = handle_msg(file, opts); !res) return res; /* add a separator between two messages? */ - if (opts->terminator) + if (opts.view.terminate) g_print("%c", VIEW_TERMINATOR); } @@ -212,17 +190,11 @@ cmd_view(const MuConfig* opts) } static Mu::Result -cmd_mkdir(const MuConfig* opts) +cmd_mkdir(const Options& opts) { - int i; - - if (!opts->params[1]) - return Err(Error::Code::InvalidArgument, - "missing directory parameter"); - - for (i = 1; opts->params[i]; ++i) { + for (auto&& dir: opts.mkdir.dirs) { if (auto&& res = - maildir_mkdir(opts->params[i], opts->dirmode, FALSE); !res) + maildir_mkdir(dir, opts.mkdir.mode); !res) return res; } @@ -230,42 +202,29 @@ cmd_mkdir(const MuConfig* opts) } static Result -cmd_add(Mu::Store& store, const MuConfig* opts) +cmd_add(Mu::Store& store, const Options& opts) { - /* note: params[0] will be 'add' */ - if (!opts->params[0] || !opts->params[1]) - return Err(Error::Code::InvalidArgument, - "expected some files to add"); - - for (auto u = 1; opts->params[u]; ++u) { - - const auto docid{store.add_message(opts->params[u])}; + for (auto&& file: opts.add.files) { + const auto docid{store.add_message(file)}; if (!docid) return Err(docid.error()); else g_debug("added message @ %s, docid=%u", - opts->params[u], docid.value()); + file.c_str(), docid.value()); } return Ok(); } static Result -cmd_remove(Mu::Store& store, const MuConfig* opts) +cmd_remove(Mu::Store& store, const Options& opts) { - /* note: params[0] will be 'remove' */ - if (!opts->params[0] || !opts->params[1]) - return Err(Error::Code::InvalidArgument, - "expected some files to remove"); - - for (auto u = 1; opts->params[u]; ++u) { - - const auto res = store.remove_message(opts->params[u]); + for (auto&& file: opts.remove.files) { + const auto res = store.remove_message(file); if (!res) - return Err(Error::Code::File, "failed to remove %s", - opts->params[u]); + return Err(Error::Code::File, "failed to remove %s", file.c_str()); else - g_debug("removed message @ %s", opts->params[u]); + g_debug("removed message @ %s", file.c_str()); } return Ok(); @@ -286,9 +245,9 @@ key_val(const Mu::MaybeAnsi& col, const std::string& key, T val) static void -print_signature(const Mu::MimeSignature& sig, const MuConfig *opts) +print_signature(const Mu::MimeSignature& sig, const Options& opts) { - Mu::MaybeAnsi col{!opts->nocolor}; + Mu::MaybeAnsi col{!opts.nocolor}; const auto created{sig.created()}; key_val(col, "created", @@ -318,10 +277,10 @@ print_signature(const Mu::MimeSignature& sig, const MuConfig *opts) static bool -verify(const MimeMultipartSigned& sigpart, const MuConfig *opts) +verify(const MimeMultipartSigned& sigpart, const Options& opts) { using VFlags = MimeMultipartSigned::VerifyFlags; - const auto vflags{opts->auto_retrieve ? + const auto vflags{opts.verify.auto_retrieve ? VFlags::EnableKeyserverLookups: VFlags::None}; auto ctx{MimeCryptoContext::make_gpg()}; @@ -329,11 +288,11 @@ verify(const MimeMultipartSigned& sigpart, const MuConfig *opts) return false; const auto sigs{sigpart.verify(*ctx, vflags)}; - Mu::MaybeAnsi col{!opts->nocolor}; + Mu::MaybeAnsi col{!opts.nocolor}; if (!sigs || sigs->empty()) { - if (!opts->quiet) + if (!opts.quiet) g_print("cannot find signatures in part\n"); return true; @@ -344,10 +303,10 @@ verify(const MimeMultipartSigned& sigpart, const MuConfig *opts) const auto status{sig.status()}; - if (!opts->quiet) + if (!opts.quiet) key_val(col, "status", to_string(status)); - if (opts->verbose) + if (opts.verbose) print_signature(sig, opts); if (none_of(sig.status() & MimeSignature::Status::Green)) @@ -358,42 +317,44 @@ verify(const MimeMultipartSigned& sigpart, const MuConfig *opts) } static Mu::Result -cmd_verify(const MuConfig* opts) +cmd_verify(const Options& opts) { - if (!opts || opts->cmd != MU_CONFIG_CMD_VERIFY) - return Err(Error::Code::Internal, "error in parameters"); + bool all_ok{true}; + const auto mopts = message_options(opts.verify); - if (!opts->params[1]) - return Err(Error::Code::InvalidArgument, - "missing message-file parameter"); + for (auto&& file: opts.verify.files) { - auto message{Message::make_from_path(opts->params[1], - mu_config_message_options(opts))}; - if (!message) - return Err(message.error()); + auto message{Message::make_from_path(file, mopts)}; + if (!message) + return Err(message.error()); + if (!opts.quiet && opts.verify.files.size() > 1) + g_print("verifying %sn\n", file.c_str()); - if (none_of(message->flags() & Flags::Signed)) { - if (!opts->quiet) - g_print("no signed parts found\n"); - return Ok(); + if (none_of(message->flags() & Flags::Signed)) { + if (!opts.quiet) + g_print("%s: no signed parts found\n", file.c_str()); + continue; + } + + bool verified{true}; /* innocent until proven guilty */ + for(auto&& part: message->parts()) { + + if (!part.is_signed()) + continue; + + const auto& mobj{part.mime_object()}; + if (!mobj.is_multipart_signed()) + continue; + + if (!verify(MimeMultipartSigned(mobj), opts)) + verified = false; + } + + all_ok = all_ok && verified; } - bool verified{true}; /* innocent until proven guilty */ - for(auto&& part: message->parts()) { - - if (!part.is_signed()) - continue; - - const auto& mobj{part.mime_object()}; - if (!mobj.is_multipart_signed()) - continue; - - if (!verify(MimeMultipartSigned(mobj), opts)) - verified = false; - } - - if (verified) + if (all_ok) return Ok(); else return Err(Error::Code::UnverifiedSignature, @@ -401,7 +362,7 @@ cmd_verify(const MuConfig* opts) } static Result -cmd_info(const Mu::Store& store, const MuConfig* opts) +cmd_info(const Mu::Store& store, const Options& opts) { using namespace tabulate; @@ -442,7 +403,7 @@ cmd_info(const Mu::Store& store, const MuConfig* opts) info.add_row({"last-change", tstamp(store.statistics().last_change)}); info.add_row({"last-index", tstamp(store.statistics().last_index)}); - if (!opts->nocolor) + if (!opts.nocolor) colorify(info); std::cout << info << '\n'; @@ -451,38 +412,24 @@ cmd_info(const Mu::Store& store, const MuConfig* opts) } static Result -cmd_init(const MuConfig* opts) +cmd_init(const Options& opts) { /* not provided, nor could we find a good default */ - if (!opts->maildir) + if (opts.init.maildir.empty()) return Err(Error::Code::InvalidArgument, "missing --maildir parameter and could " "not determine default"); - if (opts->max_msg_size < 0) - return Err(Error::Code::InvalidArgument, - "invalid value for max-message-size"); - else if (opts->batch_size < 0) - return Err(Error::Code::InvalidArgument, - "invalid value for batch-size"); - Mu::Store::Config conf{}; - conf.max_message_size = opts->max_msg_size; - conf.batch_size = opts->batch_size; + conf.max_message_size = opts.init.max_msg_size.value_or(0); + conf.batch_size = opts.init.batch_size.value_or(0); - Mu::StringVec my_addrs; - auto addrs = opts->my_addresses; - while (addrs && *addrs) { - my_addrs.emplace_back(*addrs); - ++addrs; - } - - auto store = Store::make_new(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), - opts->maildir, my_addrs, conf); + auto store = Store::make_new(opts.runtime_path(RuntimePath::XapianDb), + opts.init.maildir, opts.init.my_addresses, conf); if (!store) return Err(store.error()); - if (!opts->quiet) { + if (!opts.quiet) { cmd_info(*store, opts); std::cout << "\nstore created; use the 'index' command to fill/update it.\n"; } @@ -491,9 +438,9 @@ cmd_init(const MuConfig* opts) } static Result -cmd_find(const MuConfig* opts) +cmd_find(const Options& opts) { - auto store{Store::make(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB))}; + auto store{Store::make(opts.runtime_path(RuntimePath::XapianDb))}; if (!store) return Err(store.error()); else @@ -511,13 +458,13 @@ show_usage(void) } -using ReadOnlyStoreFunc = std::function(const Store&, const MuConfig*)>; -using WritableStoreFunc = std::function(Store&, const MuConfig*)>; +using ReadOnlyStoreFunc = std::function(const Store&, const Options&)>; +using WritableStoreFunc = std::function(Store&, const Options&)>; static Result -with_readonly_store(const ReadOnlyStoreFunc& func, const MuConfig* opts) +with_readonly_store(const ReadOnlyStoreFunc& func, const Options& opts) { - auto store{Store::make(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB))}; + auto store{Store::make(opts.runtime_path(RuntimePath::XapianDb))}; if (!store) return Err(store.error()); @@ -525,9 +472,9 @@ with_readonly_store(const ReadOnlyStoreFunc& func, const MuConfig* opts) } static Result -with_writable_store(const WritableStoreFunc func, const MuConfig* opts) +with_writable_store(const WritableStoreFunc func, const Options& opts) { - auto store{Store::make(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), + auto store{Store::make(opts.runtime_path(RuntimePath::XapianDb), Store::Options::Writable)}; if (!store) return Err(store.error()); @@ -536,54 +483,53 @@ with_writable_store(const WritableStoreFunc func, const MuConfig* opts) } Result -Mu::mu_cmd_execute(const MuConfig* opts) try { +Mu::mu_cmd_execute(const Options& opts) try { - if (!opts || !opts->params || !opts->params[0]) - return Err(Error::Code::InvalidArgument, "error in parameters"); - - switch (opts->cmd) { - case MU_CONFIG_CMD_HELP: /* already handled in mu-config.c */ - return Ok(); + if (!opts.sub_command) + return Err(Error::Code::Internal, "missing subcommand"); + switch (*opts.sub_command) { + case Options::SubCommand::Help: + return Ok(); /* already handled in mu-options.cc */ /* * no store needed */ - case MU_CONFIG_CMD_FIELDS: + case Options::SubCommand::Fields: return mu_cmd_fields(opts); - case MU_CONFIG_CMD_MKDIR: + case Options::SubCommand::Mkdir: return cmd_mkdir(opts); - case MU_CONFIG_CMD_SCRIPT: + case Options::SubCommand::Script: return mu_cmd_script(opts); - case MU_CONFIG_CMD_VIEW: + case Options::SubCommand::View: return cmd_view(opts); - case MU_CONFIG_CMD_VERIFY: + case Options::SubCommand::Verify: return cmd_verify(opts); - case MU_CONFIG_CMD_EXTRACT: + case Options::SubCommand::Extract: return mu_cmd_extract(opts); /* * read-only store */ - case MU_CONFIG_CMD_CFIND: + case Options::SubCommand::Cfind: return with_readonly_store(mu_cmd_cfind, opts); - case MU_CONFIG_CMD_FIND: + case Options::SubCommand::Find: return cmd_find(opts); - case MU_CONFIG_CMD_INFO: + case Options::SubCommand::Info: return with_readonly_store(cmd_info, opts); /* writable store */ - case MU_CONFIG_CMD_ADD: + case Options::SubCommand::Add: return with_writable_store(cmd_add, opts); - case MU_CONFIG_CMD_REMOVE: + case Options::SubCommand::Remove: return with_writable_store(cmd_remove, opts); - case MU_CONFIG_CMD_INDEX: + case Options::SubCommand::Index: return with_writable_store(mu_cmd_index, opts); /* commands instantiate store themselves */ - case MU_CONFIG_CMD_INIT: + case Options::SubCommand::Init: return cmd_init(opts); - case MU_CONFIG_CMD_SERVER: + case Options::SubCommand::Server: return mu_cmd_server(opts); default: diff --git a/mu/mu-cmd.hh b/mu/mu-cmd.hh index 12b2ddf4..3fc57c77 100644 --- a/mu/mu-cmd.hh +++ b/mu/mu-cmd.hh @@ -21,11 +21,37 @@ #define MU_CMD_HH__ #include -#include #include #include +#include "mu-options.hh" + namespace Mu { + + +/** + * Get message options from (sub)command options + * + * @param cmdopts (sub) command options + * + * @return message options + */ +template +constexpr Message::Options +message_options(const CmdOpts& cmdopts) +{ + Message::Options mopts{}; + + if (cmdopts.decrypt) + mopts |= Message::Options::Decrypt; + if (cmdopts.auto_retrieve) + mopts |= Message::Options::RetrieveKeys; + + return mopts; +} + + + /** * execute the 'find' command * @@ -34,7 +60,7 @@ namespace Mu { * * @return Ok() or some error */ -Result mu_cmd_find(const Mu::Store& store, const MuConfig* opts); +Result mu_cmd_find(const Store& store, const Options& opts); /** * execute the 'extract' command @@ -43,7 +69,7 @@ Result mu_cmd_find(const Mu::Store& store, const MuConfig* opts); * * @return Ok() or some error */ -Result mu_cmd_extract(const MuConfig* opts); +Result mu_cmd_extract(const Options& opts); /** * execute the 'fields' command @@ -52,7 +78,7 @@ Result mu_cmd_extract(const MuConfig* opts); * * @return Ok() or some error */ -Result mu_cmd_fields(const MuConfig* opts); +Result mu_cmd_fields(const Options& opts); /** * execute the 'script' command @@ -62,7 +88,7 @@ Result mu_cmd_fields(const MuConfig* opts); * * @return Ok() or some error */ -Result mu_cmd_script(const MuConfig* opts); +Result mu_cmd_script(const Options& opts); /** * execute the cfind command @@ -72,7 +98,7 @@ Result mu_cmd_script(const MuConfig* opts); * * @return Ok() or some error */ -Result mu_cmd_cfind(const Mu::Store& store, const MuConfig* opts); +Result mu_cmd_cfind(const Store& store, const Options& opts); /** * execute the 'index' command @@ -82,16 +108,16 @@ Result mu_cmd_cfind(const Mu::Store& store, const MuConfig* opts); * * @return Ok() or some error */ -Result mu_cmd_index(Mu::Store& store, const MuConfig* opt); +Result mu_cmd_index(Store& store, const Options& opt); /** * execute the server command * @param opts configuration options * @param err receives error information, or NULL * - * @return MU_OK (0) if the command succeeds, some error code otherwise + * @return Ok() or some error */ -Result mu_cmd_server(const MuConfig* opts); +Result mu_cmd_server(const Options& opts); /** * execute some mu command, based on 'opts' @@ -101,7 +127,7 @@ Result mu_cmd_server(const MuConfig* opts); * * @return Ok() or some error */ -Result mu_cmd_execute(const MuConfig* opts); +Result mu_cmd_execute(const Options& opts); } // namespace Mu diff --git a/mu/mu-config.cc b/mu/mu-config.cc deleted file mode 100644 index 2dcb3a3d..00000000 --- a/mu/mu-config.cc +++ /dev/null @@ -1,738 +0,0 @@ -/* -** Copyright (C) 2008-2022 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 -#include /* memset */ -#include -#include - -#include "mu-config.hh" -#include "mu-cmd.hh" - -using namespace Mu; - -static MuConfig MU_CONFIG; - -#define color_maybe(C) (MU_CONFIG.nocolor ? "" : (C)) - -static MuConfigFormat -get_output_format(const char* formatstr) -{ - int i; - struct { - const char* name; - MuConfigFormat format; - } formats[] = {{"mutt-alias", MU_CONFIG_FORMAT_MUTT_ALIAS}, - {"mutt-ab", MU_CONFIG_FORMAT_MUTT_AB}, - {"wl", MU_CONFIG_FORMAT_WL}, - {"csv", MU_CONFIG_FORMAT_CSV}, - {"org-contact", MU_CONFIG_FORMAT_ORG_CONTACT}, - {"bbdb", MU_CONFIG_FORMAT_BBDB}, - {"links", MU_CONFIG_FORMAT_LINKS}, - {"plain", MU_CONFIG_FORMAT_PLAIN}, - {"sexp", MU_CONFIG_FORMAT_SEXP}, - {"json", MU_CONFIG_FORMAT_JSON}, - {"xml", MU_CONFIG_FORMAT_XML}, - {"xquery", MU_CONFIG_FORMAT_XQUERY}, - {"mquery", MU_CONFIG_FORMAT_MQUERY}, - {"debug", MU_CONFIG_FORMAT_DEBUG}}; - - for (i = 0; i != G_N_ELEMENTS(formats); i++) - if (strcmp(formats[i].name, formatstr) == 0) - return formats[i].format; - - return MU_CONFIG_FORMAT_UNKNOWN; -} - -#define expand_dir(D) \ - if ((D)) { \ - char* exp; \ - exp = mu_util_dir_expand((D)); \ - if (exp) { \ - g_free((D)); \ - (D) = exp; \ - } \ - } - -static void -set_group_mu_defaults() -{ - /* try to determine muhome from command-line or environment; - * note: if not specified, we use XDG defaults */ - - if (!MU_CONFIG.muhome) { - /* if not set explicity, try the environment */ - const char* muhome; - muhome = g_getenv("MUHOME"); - if (muhome) - MU_CONFIG.muhome = g_strdup(muhome); - } - - if (MU_CONFIG.muhome) - expand_dir(MU_CONFIG.muhome); - - /* check for the MU_NOCOLOR or NO_COLOR env vars; but in any case don't - * use colors unless we're writing to a tty */ - if (g_getenv(MU_NOCOLOR) != NULL || g_getenv("NO_COLOR") != NULL) - MU_CONFIG.nocolor = TRUE; - - if (!isatty(fileno(stdout)) || !isatty(fileno(stderr))) - MU_CONFIG.nocolor = TRUE; -} - -static GOptionGroup* -config_options_group_mu() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"debug", 'd', 0, G_OPTION_ARG_NONE, &MU_CONFIG.debug, - "print debug output to standard error (false)", NULL}, - {"quiet", 'q', 0, G_OPTION_ARG_NONE, &MU_CONFIG.quiet, - "don't give any progress information (false)", NULL}, - {"version", 'V', 0, G_OPTION_ARG_NONE, &MU_CONFIG.version, - "display version and copyright information (false)", NULL}, - {"muhome", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.muhome, - "specify an alternative mu directory", ""}, - {"log-stderr", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.log_stderr, - "log to standard error (false)", NULL}, - {"nocolor", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.nocolor, - "don't use ANSI-colors in output (false)", NULL}, - {"verbose", 'v', 0, G_OPTION_ARG_NONE, &MU_CONFIG.verbose, - "verbose output (false)", NULL}, - - {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.params, - "parameters", NULL}, - {NULL, 0, 0, (GOptionArg)0, NULL, NULL, NULL}}; - - og = g_option_group_new("mu", "general mu options", "", NULL, NULL); - g_option_group_add_entries(og, entries); - - return og; -} - -static void -set_group_init_defaults() -{ - if (!MU_CONFIG.maildir) - MU_CONFIG.maildir = mu_util_guess_maildir(); - - expand_dir(MU_CONFIG.maildir); -} - -static GOptionGroup* -config_options_group_init() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"maildir", 'm', 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.maildir, - "top of the maildir", ""}, - {"my-address", 0, 0, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.my_addresses, - "my e-mail address; can be used multiple times", "
"}, - {"max-message-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.max_msg_size, - "Maximum allowed size for messages", ""}, - {"batch-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.batch_size, - "Number of changes in a database transaction batch", ""}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("init", "Options for the 'init' command", "", NULL, NULL); - g_option_group_add_entries(og, entries); - - return og; -} - -static gboolean -index_post_parse_func(GOptionContext* context, GOptionGroup* group, gpointer data, - GError** error) -{ - if (!MU_CONFIG.maildir && !MU_CONFIG.my_addresses) - return TRUE; - - g_printerr("%sNOTE%s: as of mu 1.3.8, 'mu index' no longer uses the\n" - "--maildir/-m or --my-address options.\n\n", - color_maybe(MU_COLOR_RED), color_maybe(MU_COLOR_DEFAULT)); - g_printerr("Instead, these options should be passed to 'mu init'.\n"); - g_printerr( - "See the mu-init(1) or the mu4e reference manual,\n'Initializing the message " - "store' for details.\n\n"); - - return TRUE; -} - -static GOptionGroup* -config_options_group_index() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - /* only here so we can tell users they are deprecated */ - {"maildir", 'm', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, - &MU_CONFIG.maildir, "top of the maildir", ""}, - {"my-address", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, - &MU_CONFIG.my_addresses, "my e-mail address; can be used multiple times", - "
"}, - - {"lazy-check", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.lazycheck, - "only check dir-timestamps (false)", NULL}, - {"nocleanup", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.nocleanup, - "don't clean up the database after indexing (false)", NULL}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("index", "Options for the 'index' command", "", NULL, - NULL); - g_option_group_add_entries(og, entries); - g_option_group_set_parse_hooks(og, NULL, (GOptionParseFunc)index_post_parse_func); - - return og; -} - -static void -set_group_find_defaults() -{ - /* note, when no fields are specified, we use date-from-subject */ - if (!MU_CONFIG.fields || !*MU_CONFIG.fields) { - MU_CONFIG.fields = g_strdup("d f s"); - if (!MU_CONFIG.sortfield) { - MU_CONFIG.sortfield = g_strdup("d"); - } - } - - if (!MU_CONFIG.formatstr) /* by default, use plain output */ - MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN; - else - MU_CONFIG.format = get_output_format(MU_CONFIG.formatstr); - - expand_dir(MU_CONFIG.linksdir); -} - -static GOptionGroup* -config_options_group_find() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"fields", 'f', 0, G_OPTION_ARG_STRING, &MU_CONFIG.fields, - "fields to display in the output", ""}, - {"sortfield", 's', 0, G_OPTION_ARG_STRING, &MU_CONFIG.sortfield, - "field to sort on", ""}, - {"maxnum", 'n', 0, G_OPTION_ARG_INT, &MU_CONFIG.maxnum, - "number of entries to display in the output", ""}, - {"threads", 't', 0, G_OPTION_ARG_NONE, &MU_CONFIG.threads, - "show message threads", NULL}, - {"bookmark", 'b', 0, G_OPTION_ARG_STRING, &MU_CONFIG.bookmark, - "use a bookmarked query", ""}, - {"reverse", 'z', 0, G_OPTION_ARG_NONE, &MU_CONFIG.reverse, - "sort in reverse (descending) order (z -> a)", NULL}, - {"skip-dups", 'u', 0, G_OPTION_ARG_NONE, &MU_CONFIG.skip_dups, - "show only the first of messages duplicates (false)", NULL}, - {"include-related", 'r', 0, G_OPTION_ARG_NONE, &MU_CONFIG.include_related, - "include related messages in results (false)", NULL}, - {"linksdir", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.linksdir, - "output as symbolic links to a target maildir", ""}, - {"clearlinks", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.clearlinks, - "clear old links before filling a linksdir (false)", NULL}, - {"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr, - "output format ('plain'(*), 'links', 'xml'," - "'sexp', 'xquery')", - ""}, - {"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len, - "use up to lines for the summary, or 0 for none (0)", ""}, - {"exec", 'e', 0, G_OPTION_ARG_STRING, &MU_CONFIG.exec, - "execute command on each match message", ""}, - {"after", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.after, - "only show messages whose m_time > T (t_time)", ""}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("find", "Options for the 'find' command", "", NULL, NULL); - g_option_group_add_entries(og, entries); - - return og; -} - -static GOptionGroup* -config_options_group_mkdir() -{ - GOptionGroup* og; - GOptionEntry entries[] = {{"mode", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.dirmode, - "set the mode (as in chmod), in octal notation", - ""}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - /* set dirmode before, because '0000' is a valid mode */ - MU_CONFIG.dirmode = 0755; - - og = g_option_group_new("mkdir", "Options for the 'mkdir' command", "", NULL, - NULL); - g_option_group_add_entries(og, entries); - - return og; -} - -static void -set_group_cfind_defaults() -{ - if (!MU_CONFIG.formatstr) /* by default, use plain output */ - MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN; - else - MU_CONFIG.format = get_output_format(MU_CONFIG.formatstr); -} - -static GOptionGroup* -config_options_group_cfind() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr, - "output format (plain(*), mutt-alias, mutt-ab, wl, " - "org-contact, bbdb, csv)", - ""}, - {"personal", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.personal, - "whether to only get 'personal' contacts", NULL}, - {"after", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.after, - "only get addresses last seen after T", ""}, - {"maxnum", 'n', 0, G_OPTION_ARG_INT, &MU_CONFIG.maxnum, - "maximum number of contacts", ""}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("cfind", "Options for the 'cfind' command", "", NULL, - NULL); - g_option_group_add_entries(og, entries); - - return og; -} - -static GOptionGroup* -config_options_group_script() -{ - GOptionGroup* og; - GOptionEntry entries[] = {{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, - &MU_CONFIG.params, "script parameters", NULL}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("script", "Options for the 'script' command", "", NULL, - NULL); - - g_option_group_add_entries(og, entries); - - return og; -} - -static void -set_group_view_defaults() -{ - if (!MU_CONFIG.formatstr) /* by default, use plain output */ - MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN; - else - MU_CONFIG.format = get_output_format(MU_CONFIG.formatstr); -} - -/* crypto options are used in a few different commands */ -static GOptionEntry* -crypto_option_entries() -{ - static GOptionEntry entries[] = { - {"auto-retrieve", 'r', 0, G_OPTION_ARG_NONE, &MU_CONFIG.auto_retrieve, - "attempt to retrieve keys online (false)", NULL}, - {"decrypt", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.decrypt, - "attempt to decrypt the message", NULL}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - return entries; -} - -static GOptionGroup* -config_options_group_view() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len, - "use up to lines for the summary, or 0 for none (0)", ""}, - {"terminate", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.terminator, - "terminate messages with ascii-0x07 (\\f, form-feed)", NULL}, - {"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr, - "output format ('plain'(*), 'sexp')", ""}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("view", "Options for the 'view' command", "", NULL, NULL); - - g_option_group_add_entries(og, entries); - g_option_group_add_entries(og, crypto_option_entries()); - - return og; -} - -static void -set_group_extract_defaults() -{ - if (!MU_CONFIG.targetdir) - MU_CONFIG.targetdir = g_strdup("."); - - expand_dir(MU_CONFIG.targetdir); -} - -static GOptionGroup* -config_options_group_extract() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"save-attachments", 'a', 0, G_OPTION_ARG_NONE, &MU_CONFIG.save_attachments, - "save all attachments (false)", NULL}, - {"save-all", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.save_all, - "save all parts (incl. non-attachments) (false)", NULL}, - {"parts", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.parts, - "save specific parts (comma-separated list)", ""}, - {"target-dir", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.targetdir, - "target directory for saving", ""}, - {"overwrite", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.overwrite, - "overwrite existing files (false)", NULL}, - {"play", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.play, - "try to 'play' (open) the extracted parts", NULL}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - og = g_option_group_new("extract", "Options for the 'extract' command", "", NULL, - NULL); - g_option_group_add_entries(og, entries); - g_option_group_add_entries(og, crypto_option_entries()); - - return og; -} - -static GOptionGroup* -config_options_group_verify() -{ - GOptionGroup* og; - og = g_option_group_new("verify", "Options for the 'verify' command", "", NULL, - NULL); - g_option_group_add_entries(og, crypto_option_entries()); - - return og; -} - -static GOptionGroup* -config_options_group_server() -{ - GOptionGroup* og; - GOptionEntry entries[] = { - {"commands", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.commands, - "list the available command and their parameters, then exit", NULL}, - {"eval", 'e', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &MU_CONFIG.eval, - "expression to evaluate", ""}, - {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}}; - - og = g_option_group_new("server", "Options for the 'server' command", "", NULL, - NULL); - g_option_group_add_entries(og, entries); - - return og; -} - -static MuConfigCmd -cmd_from_string(const char* str) -{ - int i; - struct { - const gchar* name; - MuConfigCmd cmd; - } cmd_map[] = { - {"add", MU_CONFIG_CMD_ADD}, {"cfind", MU_CONFIG_CMD_CFIND}, - {"extract", MU_CONFIG_CMD_EXTRACT}, {"find", MU_CONFIG_CMD_FIND}, - {"help", MU_CONFIG_CMD_HELP}, {"index", MU_CONFIG_CMD_INDEX}, - {"info", MU_CONFIG_CMD_INFO}, {"init", MU_CONFIG_CMD_INIT}, - {"mkdir", MU_CONFIG_CMD_MKDIR}, {"remove", MU_CONFIG_CMD_REMOVE}, - {"script", MU_CONFIG_CMD_SCRIPT}, {"server", MU_CONFIG_CMD_SERVER}, - {"verify", MU_CONFIG_CMD_VERIFY}, {"view", MU_CONFIG_CMD_VIEW}, - {"fields", MU_CONFIG_CMD_FIELDS}, - }; - - if (!str) - return MU_CONFIG_CMD_UNKNOWN; - - for (i = 0; i != G_N_ELEMENTS(cmd_map); ++i) - if (strcmp(str, cmd_map[i].name) == 0) - return cmd_map[i].cmd; -#ifdef BUILD_GUILE - /* if we don't recognize it and it's not an option, it may be - * some script */ - if (str[0] != '-') - return MU_CONFIG_CMD_SCRIPT; -#endif /*BUILD_GUILE*/ - - return MU_CONFIG_CMD_UNKNOWN; -} - -static gboolean -parse_cmd(int* argcp, char*** argvp, GError** err) -{ - MU_CONFIG.cmd = MU_CONFIG_CMD_NONE; - MU_CONFIG.cmdstr = NULL; - - if (*argcp < 2) /* no command found at all */ - return TRUE; - else if ((**argvp)[1] == '-') - /* if the first param starts with '-', there is no - * command, just some option (like --version, --help - * etc.)*/ - return TRUE; - - MU_CONFIG.cmdstr = g_strdup((*argvp)[1]); - MU_CONFIG.cmd = cmd_from_string(MU_CONFIG.cmdstr); - -#ifndef BUILD_GUILE - if (MU_CONFIG.cmd == MU_CONFIG_CMD_SCRIPT) { - mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, - "command 'script' not supported"); - return FALSE; - } -#endif /*!BUILD_GUILE*/ - - if (MU_CONFIG.cmdstr && MU_CONFIG.cmdstr[0] != '-' && - MU_CONFIG.cmd == MU_CONFIG_CMD_UNKNOWN) { - mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, "unknown command '%s'", - MU_CONFIG.cmdstr); - return FALSE; - } - - return TRUE; -} - -static GOptionGroup* -get_option_group(MuConfigCmd cmd) -{ - switch (cmd) { - case MU_CONFIG_CMD_CFIND: return config_options_group_cfind(); - case MU_CONFIG_CMD_EXTRACT: return config_options_group_extract(); - case MU_CONFIG_CMD_FIND: return config_options_group_find(); - case MU_CONFIG_CMD_INDEX: return config_options_group_index(); - case MU_CONFIG_CMD_INIT: return config_options_group_init(); - case MU_CONFIG_CMD_MKDIR: return config_options_group_mkdir(); - case MU_CONFIG_CMD_SERVER: return config_options_group_server(); - case MU_CONFIG_CMD_SCRIPT: return config_options_group_script(); - case MU_CONFIG_CMD_VERIFY: return config_options_group_verify(); - case MU_CONFIG_CMD_VIEW: return config_options_group_view(); - default: return NULL; /* no group to add */ - } -} - -/* ugh yuck massaging the GOption text output; glib prepares some text - * which has a 'Usage:' for the 'help' command. However, we need the - * help for the command we're asking help for. So, we remove the Usage: - * from what glib generates. :-( */ -static gchar* -massage_help(const char* help) -{ - GRegex* rx; - char* str; - - rx = g_regex_new("^Usage:.*\n.*\n", (GRegexCompileFlags)0, - G_REGEX_MATCH_NEWLINE_ANY, NULL); - str = g_regex_replace(rx, help, -1, 0, "", G_REGEX_MATCH_NEWLINE_ANY, NULL); - g_regex_unref(rx); - return str; -} - -static const char* -get_help_string(MuConfigCmd cmd, bool long_help) -{ - struct Help { - MuConfigCmd cmd; - const char* usage; - const char* long_help; - }; - constexpr std::array all_help = { -#include "mu-help-strings.inc" - }; - - const auto help_it = std::find_if(all_help.begin(), all_help.end(), - [&](auto&& info) { return info.cmd == cmd; }); - if (help_it == all_help.end()) { - g_critical("cannot find info for %u", cmd); - return ""; - } else - return long_help ? help_it->long_help : help_it->usage; -} - -void -Mu::mu_config_show_help(MuConfigCmd cmd) -{ - GOptionContext* ctx; - GOptionGroup* group; - char * help, *cleanhelp; - - g_return_if_fail(mu_config_cmd_is_valid(cmd)); - - ctx = g_option_context_new("- mu help"); - g_option_context_set_main_group(ctx, config_options_group_mu()); - - group = get_option_group(cmd); - if (group) - g_option_context_add_group(ctx, group); - - g_option_context_set_description(ctx, get_help_string(cmd, TRUE)); - help = g_option_context_get_help(ctx, TRUE, group); - cleanhelp = massage_help(help); - - g_print("usage:\n\t%s%s", get_help_string(cmd, FALSE), cleanhelp); - - g_free(help); - g_free(cleanhelp); - g_option_context_free(ctx); -} - -static gboolean -cmd_help() -{ - MuConfigCmd cmd; - - if (!MU_CONFIG.params) - cmd = MU_CONFIG_CMD_UNKNOWN; - else - cmd = cmd_from_string(MU_CONFIG.params[1]); - - if (cmd == MU_CONFIG_CMD_UNKNOWN) { - mu_config_show_help(MU_CONFIG_CMD_HELP); - return TRUE; - } - - mu_config_show_help(cmd); - - return TRUE; -} - -static gboolean -parse_params(int* argcp, char*** argvp, GError** err) -{ - GOptionContext* context; - GOptionGroup* group; - gboolean rv; - - context = g_option_context_new("- mu general options"); - - g_option_context_set_help_enabled(context, FALSE); - g_option_context_set_main_group(context, config_options_group_mu()); - g_option_context_set_ignore_unknown_options(context, FALSE); - - switch (MU_CONFIG.cmd) { - case MU_CONFIG_CMD_NONE: - case MU_CONFIG_CMD_HELP: - /* 'help' is special; sucks in the options of the - * command after it */ - rv = g_option_context_parse(context, argcp, argvp, err) && cmd_help(); - break; - case MU_CONFIG_CMD_SCRIPT: - /* all unknown commands are passed to 'script' */ - g_option_context_set_ignore_unknown_options(context, TRUE); - group = get_option_group(MU_CONFIG.cmd); - g_option_context_add_group(context, group); - rv = g_option_context_parse(context, argcp, argvp, err); - MU_CONFIG.script = g_strdup(MU_CONFIG.cmdstr); - /* argvp contains the script parameters */ - MU_CONFIG.script_params = (const char**)&((*argvp)[1]); - break; - - default: - group = get_option_group(MU_CONFIG.cmd); - if (group) - g_option_context_add_group(context, group); - - rv = g_option_context_parse(context, argcp, argvp, err); - break; - } - - g_option_context_free(context); - - return rv ? TRUE : FALSE; -} - -MuConfig* -Mu::mu_config_init(int* argcp, char*** argvp, GError** err) -{ - g_return_val_if_fail(argcp && argvp, NULL); - - memset(&MU_CONFIG, 0, sizeof(MU_CONFIG)); - - if (!parse_cmd(argcp, argvp, err)) - goto errexit; - - if (!parse_params(argcp, argvp, err)) - goto errexit; - - /* fill in the defaults if user did not specify */ - set_group_mu_defaults(); - set_group_init_defaults(); - set_group_find_defaults(); - set_group_cfind_defaults(); - set_group_view_defaults(); - set_group_extract_defaults(); - /* set_group_mkdir_defaults (config); */ - - return &MU_CONFIG; - -errexit: - mu_config_uninit(&MU_CONFIG); - return NULL; -} - -void -Mu::mu_config_uninit(MuConfig* opts) -{ - if (!opts) - return; - - g_free(opts->cmdstr); - g_free(opts->muhome); - g_free(opts->maildir); - g_free(opts->fields); - g_free(opts->sortfield); - g_free(opts->bookmark); - g_free(opts->formatstr); - g_free(opts->exec); - g_free(opts->linksdir); - g_free(opts->targetdir); - g_free(opts->parts); - g_free(opts->script); - g_free(opts->eval); - - g_strfreev(opts->my_addresses); - g_strfreev(opts->params); - - memset(opts, 0, sizeof(MU_CONFIG)); -} - -size_t -Mu::mu_config_param_num(const MuConfig* opts) -{ - size_t n; - - g_return_val_if_fail(opts && opts->params, 0); - for (n = 0; opts->params[n]; ++n) - ; - - return n; -} - -Message::Options -Mu::mu_config_message_options(const MuConfig *conf) -{ - Message::Options opts{}; - - if (conf->decrypt) - opts |= Message::Options::Decrypt; - if (conf->auto_retrieve) - opts |= Message::Options::RetrieveKeys; - - return opts; -} diff --git a/mu/mu-config.hh b/mu/mu-config.hh deleted file mode 100644 index 9b07d1e6..00000000 --- a/mu/mu-config.hh +++ /dev/null @@ -1,246 +0,0 @@ -/* -** Copyright (C) 2008-2022 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. -** -*/ - -#ifndef MU_CONFIG_HH__ -#define MU_CONFIG_HH__ - -#include -#include /* for mode_t */ -#include -#include - -namespace Mu { - -/* env var; if non-empty, color are disabled */ -#define MU_NOCOLOR "MU_NOCOLOR" - -typedef enum { - MU_CONFIG_FORMAT_UNKNOWN = 0, - - /* for cfind, find, view */ - MU_CONFIG_FORMAT_PLAIN, /* plain output */ - - /* for cfind */ - MU_CONFIG_FORMAT_MUTT_ALIAS, /* mutt alias style */ - MU_CONFIG_FORMAT_MUTT_AB, /* mutt ext abook */ - MU_CONFIG_FORMAT_WL, /* Wanderlust abook */ - MU_CONFIG_FORMAT_CSV, /* comma-sep'd values */ - MU_CONFIG_FORMAT_ORG_CONTACT, /* org-contact */ - MU_CONFIG_FORMAT_BBDB, /* BBDB */ - MU_CONFIG_FORMAT_DEBUG, - - /* for find, view */ - MU_CONFIG_FORMAT_SEXP, /* output sexps (emacs) */ - MU_CONFIG_FORMAT_JSON, /* output JSON */ - - /* for find */ - MU_CONFIG_FORMAT_LINKS, /* output as symlinks */ - MU_CONFIG_FORMAT_XML, /* output xml */ - MU_CONFIG_FORMAT_XQUERY, /* output the xapian query */ - MU_CONFIG_FORMAT_MQUERY, /* output the mux query */ - - MU_CONFIG_FORMAT_EXEC /* execute some command */ -} MuConfigFormat; - -typedef enum { - MU_CONFIG_CMD_UNKNOWN = 0, - - MU_CONFIG_CMD_ADD, - MU_CONFIG_CMD_CFIND, - MU_CONFIG_CMD_EXTRACT, - MU_CONFIG_CMD_FIELDS, - MU_CONFIG_CMD_FIND, - MU_CONFIG_CMD_HELP, - MU_CONFIG_CMD_INDEX, - MU_CONFIG_CMD_INFO, - MU_CONFIG_CMD_INIT, - MU_CONFIG_CMD_MKDIR, - MU_CONFIG_CMD_REMOVE, - MU_CONFIG_CMD_SCRIPT, - MU_CONFIG_CMD_SERVER, - MU_CONFIG_CMD_VERIFY, - MU_CONFIG_CMD_VIEW, - - MU_CONFIG_CMD_NONE -} MuConfigCmd; - -#define mu_config_cmd_is_valid(C) ((C) > MU_CONFIG_CMD_UNKNOWN && (C) < MU_CONFIG_CMD_NONE) - -/* struct with all configuration options for mu; it will be filled - * from the config file, and/or command line arguments */ - -struct _MuConfig { - MuConfigCmd cmd; /* the command, or - * MU_CONFIG_CMD_NONE */ - char* cmdstr; /* cmd string, for user - * info */ - /* general options */ - gboolean quiet; /* don't give any output */ - gboolean debug; /* log debug-level info */ - gchar* muhome; /* the House of Mu */ - gboolean version; /* request mu version */ - gboolean log_stderr; /* log to stderr (not logfile) */ - gchar** params; /* parameters (for querying) */ - gboolean nocolor; /* don't use use ansi-colors - * in some output */ - gboolean verbose; /* verbose output */ - - /* options for init */ - gchar* maildir; /* where the mails are */ - char** my_addresses; /* 'my e-mail address', for mu cfind; - * can be use multiple times */ - int max_msg_size; /* maximum size for message files */ - int batch_size; /* database transaction batch size */ - - /* options for indexing */ - - gboolean nocleanup; /* don't cleanup del'd mails from db */ - gboolean lazycheck; /* don't check dirs with up-to-date - * timestamps */ - - /* options for querying 'find' (and view-> 'summary') */ - gchar* fields; /* fields to show in output */ - gchar* sortfield; /* field to sort by (string) */ - int maxnum; /* max # of entries to print */ - gboolean reverse; /* sort in revers order (z->a) */ - gboolean threads; /* show message threads */ - - int summary_len; /* max # of lines for summary */ - - gchar* bookmark; /* use bookmark */ - gchar* formatstr; /* output type for find - * (plain,links,xml,json,sexp) - * and view (plain, sexp) and cfind - */ - MuConfigFormat format; /* the decoded formatstr */ - gchar* exec; /* command to execute on the - * files for the matched - * messages */ - gboolean skip_dups; /* if there are multiple - * messages with the same - * msgid, show only the first - * one */ - gboolean include_related; /* included related messages - * in results */ - /* for find and cind */ - time_t after; /* only show messages or - * addresses last seen after - * T */ - /* options for crypto - * ie, 'view', 'extract' */ - gboolean auto_retrieve; /* assume we're online */ - gboolean decrypt; /* try to decrypt the - * message body, if any */ - - /* options for view */ - gboolean terminator; /* add separator \f between - * multiple messages in mu - * view */ - - /* options for cfind (and 'find' --> "after") */ - gboolean personal; /* only show 'personal' addresses */ - /* also 'after' --> see above */ - - /* output to a maildir with symlinks */ - gchar* linksdir; /* maildir to output symlinks */ - gboolean clearlinks; /* clear a linksdir before filling */ - mode_t dirmode; /* mode for the created maildir */ - - /* options for extracting parts */ - gboolean save_all; /* extract all parts */ - gboolean save_attachments; /* extract all attachment parts */ - gchar* parts; /* comma-sep'd list of parts - * to save / open */ - gchar* targetdir; /* where to save the attachments */ - gboolean overwrite; /* should we overwrite same-named files */ - gboolean play; /* after saving, try to 'play' - * (open) the attmnt using xdgopen */ - /* for server */ - gboolean commands; /* dump documentations for server - * commands */ - gchar* eval; /* command to evaluate */ - - /* options for mu-script */ - gchar* script; /* script to run */ - const char** script_params; /* parameters for scripts */ -}; -typedef struct _MuConfig MuConfig; - -/** - * initialize a mu config object - * - * set default values for the configuration options; when you call - * mu_config_init, you should also call mu_config_uninit when the data - * is no longer needed. - * - * Note that this is _static_ data, ie., mu_config_init will always - * return the same pointer - * - * @param argcp: pointer to argc - * @param argvp: pointer to argv - * @param err: receives error information - */ -MuConfig* mu_config_init(int* argcp, char*** argvp, GError** err) G_GNUC_WARN_UNUSED_RESULT; -/** - * free the MuConfig structure - * - * @param opts a MuConfig struct, or NULL - */ -void mu_config_uninit(MuConfig* conf); - -/** - * execute the command / options in this config - * - * @param opts a MuConfig struct - * - * @return a value denoting the success/failure of the execution; - * MU_ERROR_NONE (0) for success, non-zero for a failure. This is to used for - * the exit code of the process - * - */ -MuError mu_config_execute(const MuConfig* conf); - -/** - * count the number of non-option parameters - * - * @param opts a MuConfig struct - * - * @return the number of non-option parameters, or 0 in case of error - */ -size_t mu_config_param_num(const MuConfig* conf); - -/** - * determine Message::Options from command line args - * - * @param opts a MuConfig struct - * - * @return the corresponding Message::Options - */ -Message::Options mu_config_message_options(const MuConfig* opts); - -/** - * print help text for the current command - * - * @param cmd the command to show help for - */ -void mu_config_show_help(const MuConfigCmd cmd); - -} // namespace Mu. - -#endif /*__MU_CONFIG_H__*/ diff --git a/mu/mu-help-strings.awk b/mu/mu-help-strings.awk deleted file mode 100644 index 24a69512..00000000 --- a/mu/mu-help-strings.awk +++ /dev/null @@ -1,58 +0,0 @@ -## Copyright (C) 2012-2022 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 of the License, 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. - - -## convert text blobs statements into c-strings - -BEGIN { - in_def=0; - in_string=0; - print "/* Do not edit - auto-generated. */" -} - - -/^#BEGIN/ { - printf "\tHelp {\n\t\t" $2 "," # e.g., MU_CONFIG_CMD_ADD - in_def=1 -} - -/^#STRING/ { - if (in_def== 1) { - if (in_string==1) { - print ","; - } - in_string=1 - } -} - -/^#END/ { - if (in_string==1) { - in_string=0; - } - in_def=0; - printf "\n\t},\n" -} - - -!/^#/ { - if (in_string==1) { - printf "\n\t\t\"" $0 "\\n\"" - } -} - -END { - print "/* the end */" -} diff --git a/mu/mu-help-strings.txt b/mu/mu-help-strings.txt deleted file mode 100644 index be545ff6..00000000 --- a/mu/mu-help-strings.txt +++ /dev/null @@ -1,200 +0,0 @@ -#-*-mode:org-*- -# -# Copyright (C) 2012-2022 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 of the License, 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. - - -#BEGIN MU_CONFIG_CMD_ADD -#STRING -mu add [] -#STRING -mu add is the command to add specific measage files to the database. Each of the -files must be specified with an absolute path. -#END - -#BEGIN MU_CONFIG_CMD_CFIND -#STRING -mu cfind [options] [--format=] [--personal] [--after=] [] -#STRING -mu cfind is the mu command to find contacts in the mu database and export them -for use in other programs. - - is one of: - plain - mutt-alias - mutt-ab - wl - csv - org-contact - bbdb - -'plain' is the default. - -If you specify '--personal', only addresses that were found in mails -that include 'my' e-mail address will be listed - so to exclude e.g. -mailing-list posts. Use the --my-address= option in 'mu index' to -specify what addresses are considered 'my' address. - -With '--after=T' you can tell mu to only show addresses that were seen after -T. T is a Unix timestamp. For example, to get only addresses seen after the -beginning of 2012, you could use - --after=`date +%%s -d 2012-01-01` -#END - -#BEGIN MU_CONFIG_CMD_EXTRACT -#STRING -mu extract [options] -#STRING -mu extract is the mu command to display and save message parts -(attachments), and open them with other tools. -#END - -#BEGIN MU_CONFIG_CMD_FIELDS -#STRING -mu fields -#STRING -mu fields produces a table with all messages fields and flags. This -is useful for writing query expressions. -#END - - -#BEGIN MU_CONFIG_CMD_FIND -#STRING -mu find [options] -#STRING -mu find is the mu command for searching e-mail message that were -stored earlier using mu index(1). - -Some examples: - # get all messages with 'bananas' in body, subject or recipient fields: - $ mu find bananas - - # get all messages regarding bananas from John with an attachment: - $ mu find from:john flag:attach bananas - - # get all messages with subject wombat in June 2009 - $ mu find subject:wombat date:20090601..20090630 - -See the `mu-find' and `mu-easy' man-pages for more information. -#END - -#BEGIN MU_CONFIG_CMD_HELP -#STRING -mu help -#STRING -mu help is the mu command to get help about , where -is one of: - add - add message to database - cfind - find a contact - extract - extract parts/attachments from messages - fields - show table of all query fields and flags - find - query the message database - help - get help - index - index messages - init - init the mu database - mkdir - create a maildir - remove - remove a message from the database - script - run a script (available only when mu was built with guile-support) - server - start mu server - verify - verify signatures of a message - view - view a specific message -#END - -#BEGIN MU_CONFIG_CMD_INDEX -#STRING -mu index [options] -#STRING -mu index is the mu command for scanning the contents of Maildir -directories and storing the results in a Xapian database.The -data can then be queried using mu-find(1). -#END - -#BEGIN MU_CONFIG_CMD_INIT -#STRING -mu init [options] -#STRING -mu init is the mu command for setting up the mu database. -#END - -#BEGIN MU_CONFIG_CMD_INFO -#STRING -mu init [options] -#STRING -mu info is the command for getting information about a mu database. -#END - -#BEGIN MU_CONFIG_CMD_MKDIR -#STRING -mu mkdir [options] [] -#STRING -mu mkdir is the command for creating Maildirs.It does not -use the mu database. -#END - -#BEGIN MU_CONFIG_CMD_REMOVE -#STRING -mu remove [options] [] -#STRING -mu remove is the mu command to remove messages from the database. -#END - -#BEGIN MU_CONFIG_CMD_SERVER -#STRING -mu server [options] -#STRING -mu server starts a simple shell in which one can query and -manipulate the mu database.The output of the commands is terms -of Lisp symbolic expressions (s-exps). Its main use is for -the mu4e e-mail client. -#END - -#BEGIN MU_CONFIG_CMD_SCRIPT -#STRING -mu script [] [-v] -mu [