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.
This commit is contained in:
Dirk-Jan C. Binnema 2022-11-16 22:51:15 +02:00
parent 27a474be41
commit 36f6e387ae
16 changed files with 1439 additions and 1857 deletions

View File

@ -1,4 +1,4 @@
## Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
## Copyright (C) 2021-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## 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') + '"'],

View File

@ -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<void>
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 : "<none>");
return FALSE;
}
/* only one pattern allowed */
if (opts->params[1] && opts->params[2]) {
g_printerr("usage: mu cfind [options] [<ptrn>]\n");
return FALSE;
}
return TRUE;
}
Result<void>
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);
}

View File

@ -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 <message/mu-message.hh>
@ -29,19 +28,19 @@ using namespace Mu;
static Result<void>
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<void>
save_parts(const std::string& path, Option<std::string>& 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<size_t> {
std::vector<size_t> nums;
for (auto&& numstr : split(opts->parts ? opts->parts : "", ','))
nums.emplace_back(
static_cast<size_t>(::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<std::string>& 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<void>
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<void>
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<void>
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<std::string> 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);
}

View File

@ -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<void>
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";

View File

@ -1,4 +1,4 @@
/*
/*
** Copyright (C) 2008-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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<bool(const Option<Message>& msg, const OutputInfo&,
const MuConfig*, GError**)>;
const Options&, GError**)>;
using Format = Options::Find::Format;
static Result<void>
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<QueryResults>
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<Message>& msg, const OutputInfo& info, const MuConfig* opts, GError** err)
static bool
exec_cmd(const Option<Message>& 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<Message>& msg, const OutputInfo& info, const MuConfig* opt
return rv;
}
static gchar*
resolve_bookmark(const MuConfig* opts, GError** err)
static Result<std::string>
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<std::string>
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<Message>& msg, const OutputInfo& info, const MuConfig* opts, GError** err)
output_link(const Option<Message>& 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<Message>& 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() : "<none>", 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<Message>& 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<Message>& msg, const OutputInfo& info, const MuConfig* opts, GError** err)
output_sexp(const Option<Message>& 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<Message>& msg, const OutputInfo& info, const MuConfig*
}
static bool
output_json(const Option<Message>& msg, const OutputInfo& info, const MuConfig* opts, GError** err)
output_json(const Option<Message>& 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<Message>& msg, const OutputInfo& info, const MuConfig* opts, GError** err)
output_xml(const Option<Message>& msg, const OutputInfo& info, const Options& opts, GError** err)
{
if (info.header) {
g_print("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
@ -445,29 +419,32 @@ output_xml(const Option<Message>& 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<void>
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<void>
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<void>
execute_find(const Store& store, const MuConfig* opts)
Result<void>
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 : "<none>");
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<void>
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);
}

View File

@ -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<void>
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;
}

View File

@ -26,7 +26,6 @@
#include <unistd.h>
#include "mu-runtime.hh"
#include "mu-cmd.hh"
#include "mu-server.hh"
@ -111,9 +110,9 @@ report_error(const Mu::Error& err) noexcept
Result<void>
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();

View File

@ -1,5 +1,5 @@
/*
** Copyright (C) 2010-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** Copyright (C) 2010-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <unistd.h>
#include <errno.h>
#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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<void>
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<Result<void>(const Store&, const MuConfig*)>;
using WritableStoreFunc = std::function<Result<void>(Store&, const MuConfig*)>;
using ReadOnlyStoreFunc = std::function<Result<void>(const Store&, const Options&)>;
using WritableStoreFunc = std::function<Result<void>(Store&, const Options&)>;
static Result<void>
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<void>
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<void>
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:

View File

@ -21,11 +21,37 @@
#define MU_CMD_HH__
#include <glib.h>
#include <mu-config.hh>
#include <mu-store.hh>
#include <utils/mu-result.hh>
#include "mu-options.hh"
namespace Mu {
/**
* Get message options from (sub)command options
*
* @param cmdopts (sub) command options
*
* @return message options
*/
template<typename CmdOpts>
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<void> mu_cmd_find(const Mu::Store& store, const MuConfig* opts);
Result<void> mu_cmd_find(const Store& store, const Options& opts);
/**
* execute the 'extract' command
@ -43,7 +69,7 @@ Result<void> mu_cmd_find(const Mu::Store& store, const MuConfig* opts);
*
* @return Ok() or some error
*/
Result<void> mu_cmd_extract(const MuConfig* opts);
Result<void> mu_cmd_extract(const Options& opts);
/**
* execute the 'fields' command
@ -52,7 +78,7 @@ Result<void> mu_cmd_extract(const MuConfig* opts);
*
* @return Ok() or some error
*/
Result<void> mu_cmd_fields(const MuConfig* opts);
Result<void> mu_cmd_fields(const Options& opts);
/**
* execute the 'script' command
@ -62,7 +88,7 @@ Result<void> mu_cmd_fields(const MuConfig* opts);
*
* @return Ok() or some error
*/
Result<void> mu_cmd_script(const MuConfig* opts);
Result<void> mu_cmd_script(const Options& opts);
/**
* execute the cfind command
@ -72,7 +98,7 @@ Result<void> mu_cmd_script(const MuConfig* opts);
*
* @return Ok() or some error
*/
Result<void> mu_cmd_cfind(const Mu::Store& store, const MuConfig* opts);
Result<void> mu_cmd_cfind(const Store& store, const Options& opts);
/**
* execute the 'index' command
@ -82,16 +108,16 @@ Result<void> mu_cmd_cfind(const Mu::Store& store, const MuConfig* opts);
*
* @return Ok() or some error
*/
Result<void> mu_cmd_index(Mu::Store& store, const MuConfig* opt);
Result<void> 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<void> mu_cmd_server(const MuConfig* opts);
Result<void> mu_cmd_server(const Options& opts);
/**
* execute some mu command, based on 'opts'
@ -101,7 +127,7 @@ Result<void> mu_cmd_server(const MuConfig* opts);
*
* @return Ok() or some error
*/
Result<void> mu_cmd_execute(const MuConfig* opts);
Result<void> mu_cmd_execute(const Options& opts);
} // namespace Mu

View File

@ -1,738 +0,0 @@
/*
** Copyright (C) 2008-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <glib.h>
#include <string.h> /* memset */
#include <unistd.h>
#include <stdio.h>
#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", "<dir>"},
{"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", "<maildir>"},
{"my-address", 0, 0, G_OPTION_ARG_STRING_ARRAY, &MU_CONFIG.my_addresses,
"my e-mail address; can be used multiple times", "<address>"},
{"max-message-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.max_msg_size,
"Maximum allowed size for messages", "<size-in-bytes>"},
{"batch-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.batch_size,
"Number of changes in a database transaction batch", "<number>"},
{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", "<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",
"<address>"},
{"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", "<fields>"},
{"sortfield", 's', 0, G_OPTION_ARG_STRING, &MU_CONFIG.sortfield,
"field to sort on", "<field>"},
{"maxnum", 'n', 0, G_OPTION_ARG_INT, &MU_CONFIG.maxnum,
"number of entries to display in the output", "<number>"},
{"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", "<bookmark>"},
{"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", "<dir>"},
{"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')",
"<format>"},
{"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len,
"use up to <n> lines for the summary, or 0 for none (0)", "<len>"},
{"exec", 'e', 0, G_OPTION_ARG_STRING, &MU_CONFIG.exec,
"execute command on each match message", "<command>"},
{"after", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.after,
"only show messages whose m_time > T (t_time)", "<timestamp>"},
{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",
"<mode>"},
{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)",
"<format>"},
{"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", "<timestamp>"},
{"maxnum", 'n', 0, G_OPTION_ARG_INT, &MU_CONFIG.maxnum,
"maximum number of contacts", "<number>"},
{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 <n> lines for the summary, or 0 for none (0)", "<len>"},
{"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')", "<format>"},
{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)", "<parts>"},
{"target-dir", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.targetdir,
"target directory for saving", "<dir>"},
{"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", "<expr>"},
{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;
}

View File

@ -1,246 +0,0 @@
/*
** Copyright (C) 2008-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <glib.h>
#include <sys/types.h> /* for mode_t */
#include <message/mu-message.hh>
#include <utils/mu-util.h>
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__*/

View File

@ -1,58 +0,0 @@
## Copyright (C) 2012-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## 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 */"
}

View File

@ -1,200 +0,0 @@
#-*-mode:org-*-
#
# Copyright (C) 2012-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
#
# 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 <file> [<files>]
#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=<format>] [--personal] [--after=<T>] [<pattern>]
#STRING
mu cfind is the mu command to find contacts in the mu database and export them
for use in other programs.
<format> 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] <file>
#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] <search expression>
#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 <command>
#STRING
mu help is the mu command to get help about <command>, where <command>
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] <dir> [<dirs>]
#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] <file> [<files>]
#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 [<pattern>] [-v]
mu <script-name> [<script options>]
#STRING
List the available scripts and/or run them (if mu was built with support for
scripts). With <pattern>, list only those scripts whose name or one-line
description matches it. With -v, get a longer description for each script.
Some examples:
List all available scripts matching 'month' (long descriptions):
$ mu script -v month
Run the 'msgs-per-month' script, and pass it the '--textonly' parameter:
$ mu msgs-per-month --textonly
#END
#BEGIN MU_CONFIG_CMD_VERIFY
#STRING
mu verify [options] <msgfile>
#STRING
mu verify is the mu command for verifying message signatures
(such as PGP/GPG signatures)and displaying information about them.
The command works on message files, and does not require
the message to be indexed in the database.
#END
#BEGIN MU_CONFIG_CMD_VIEW
#STRING
mu view [options] <file> [<files>]
#STRING
mu view is the mu command for displaying e-mail message files. It
works on message files and does not require the message to be
indexed in the database.
#END

780
mu/mu-options.cc Normal file
View File

@ -0,0 +1,780 @@
/*
** Copyright (C) 2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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.
**
*/
/**
* @brief Command-line handling
*
* Here we implement mu's command-line parsing based on the CLI11 library. At
* the time of writing, that library seems to be the best based on the criteria
* that it supports the features we need and is available as a header-only
* include.
*
* CLI11 can do quite a bit, and we're only scratching the surface here,
* plan is to slowly improve things.
*
* - we do quite a bit of sanity-checking, but the errors are a rather terse
* - the docs could be improved, e.g., `mu find --help` and --format/--sortfield
*
*/
#include <config.h>
#include <stdexcept>
#include <array>
#include <iostream>
#include <string_view>
#include <unistd.h>
#include <utils/mu-utils.hh>
#include <utils/mu-error.hh>
#include "utils/mu-test-utils.hh"
#include "mu-options.hh"
#include "mu-script.hh"
#include <thirdparty/CLI11.hpp>
using namespace Mu;
/*
* helpers
*/
/**
* Options-specific array-bases type that maps some enum to a <name, description> pair
*/
template<typename T, std::size_t N>
using InfoEnum = AssocPairs<T, std::pair<std::string_view, std::string_view>, N>;
/**
* Get the name (shortname) for some InfoEnum, based on the enum
*
* @param ie an InfoEnum
* @param e an enum value
*
* @return the name if found, or Nothing
*/
template<typename IE>
static constexpr Option<std::string_view>
to_name(const IE& ie, typename IE::value_type::first_type e) {
if (auto&& s{to_second(ie, e)}; s)
return s->first;
else
return Nothing;
}
/**
* Get the enum value for some InfoEnum, based on the name
*
* @param ie an InfoEnum
* @param name some name (shortname)
*
* @return the name if found, or Nothing
*/
template<typename IE>
static constexpr Option<typename IE::value_type::first_type>
to_enum(const IE& ie, std::string_view name) {
for(auto&& item: ie)
if (item.second.first == name)
return item.first;
else
return Nothing;
}
/**
* List help options for as a string, with the default marked with '(*)'
*
* @param ie infoenum
* @param default_opt default option
*
* @return
*/
template<typename IE>
static std::string
options_help(const IE& ie, typename IE::value_type::first_type default_opt)
{
std::string s;
for(auto&& item: ie) {
if (!s.empty())
s += ", ";
s += std::string{item.second.first};
if (item.first == default_opt)
s += "(*)"; /* default option */
}
return s;
}
/**
* Get map from string->type
*/
template<typename IE>
static std::unordered_map<std::string, typename IE::value_type::first_type>
options_map(const IE& ie)
{
std::unordered_map<std::string, typename IE::value_type::first_type> map;
for (auto&& item : ie)
map.emplace(std::string{item.second.first}, item.first);
return map;
}
/*
* common
*/
template<typename T>
static void
sub_crypto(CLI::App& sub, T& opts)
{
sub.add_flag("--auto-retrieve,-r", opts.auto_retrieve,
"Attempt to automatically retrieve online keys");
sub.add_flag("--decrypt", opts.decrypt,
"Attempt to decrypt");
}
/*
* subcommands
*/
static void
sub_add(CLI::App& sub, Options& opts)
{
sub.add_option("files", opts.add.files,
"Path(s) to message files(s)")
->required();
}
static void
sub_cfind(CLI::App& sub, Options& opts)
{
using Format = Options::Cfind::Format;
static constexpr InfoEnum<Format, 8> FormatInfos = {{
{ Format::Plain,
{"plain", "Plain output"}
},
{ Format::MuttAlias,
{"mutt-alias", "Mutt alias"}
},
{ Format::MuttAddressBook,
{"mutt-ab", "Mutt address book"}
},
{ Format::Wanderlust,
{"wl", "Wanderlust"}
},
{ Format::OrgContact,
{"org-contact", "org-contact"}
},
{ Format::Bbdb,
{"bbdb", "BBDB"}
},
{ Format::Csv,
{"csv", "comma-separated values"}
},
{ Format::Debug,
{"debug", "debug output"}
}
}};
const auto fhelp = options_help(FormatInfos, Format::Plain);
const auto fmap = options_map(FormatInfos);
sub.add_option("--format,-o", opts.cfind.format,
"Output format; one of " + fhelp)
->type_name("<format>")
->default_str("plain")
->default_val(Format::Plain)
->transform(CLI::CheckedTransformer(fmap));
sub.add_option("pattern", opts.cfind.rx_pattern,
"Regular expression pattern to match");
sub.add_flag("--personal,-p", opts.cfind.personal,
"Only show 'personal' contacts");
sub.add_option("--maxnum,-n", opts.cfind.maxnum,
"Maximum number of results")
->type_name("<number>")
->check(CLI::PositiveNumber);
}
static void
sub_extract(CLI::App& sub, Options& opts)
{
sub_crypto(sub, opts.extract);
sub.add_flag("--save-attachments,-a", opts.extract.save_attachments,
"Save all attachments");
sub.add_flag("--save-all", opts.extract.save_all, "Save all MIME parts")
->excludes("--save-attachments");
sub.add_flag("--overwrite", opts.extract.overwrite,
"Overwrite existing files");
sub.add_flag("--play", opts.extract.play,
"Attempt to open the extracted parts");
sub.add_option("--parts", opts.extract.parts,
"Save specific parts (comma-sep'd list)")
->type_name("<parts>")->delimiter(',');
sub.add_option("--target-dir", opts.extract.targetdir,
"Target directory for saving")
->type_name("<dir>")
->default_str("<current>")->default_val(".");
sub.add_option("message", opts.extract.message,
"Path to message file")->required()
->type_name("<message-path>");
sub.add_option("filename-rx", opts.extract.filename_rx,
"Regular expression for files to save")
->type_name("<filename-rx>")
->excludes("--parts")
->excludes("--save-attachments")
->excludes("--save-all");
}
static void
sub_fields(CLI::App& sub, Options& opts)
{
// nothing to do.
}
static void
sub_find(CLI::App& sub, Options& opts)
{
using Format = Options::Find::Format;
static constexpr InfoEnum<Format, 7> FormatInfos = {{
{ Format::Plain,
{"plain", "Plain output"}
},
{ Format::Links,
{"links", "Maildir with symbolic links"}
},
{ Format::Xml,
{"xml", "XML"}
},
{ Format::Sexp,
{"sexp", "S-expressions"}
},
{ Format::XQuery,
{"xquery", "Show Xapian query (for debugging)"}
},
{ Format::MQuery,
{"mquery", "Show mu query for (for debugging)"}
},
}};
sub.add_flag("--threads,-t", opts.find.threads,
"Show message threads");
sub.add_flag("--skip-dups,-u", opts.find.skip_dups,
"Show only one of messages with same message-id");
sub.add_flag("--include-related,-r", opts.find.include_related,
"Include related messages in results");
const auto fhelp = options_help(FormatInfos, Format::Plain);
const auto fmap = options_map(FormatInfos);
sub.add_option("--format,-o", opts.find.format,
"Output format; one of " + fhelp)
->type_name("<format>")
->default_str("plain")
->default_val(Format::Plain)
->transform(CLI::CheckedTransformer(fmap));
sub.add_option("--maxnum,-n", opts.find.maxnum,
"Maximum number of results")
->type_name("<number>")
->check(CLI::PositiveNumber);
sub.add_option("--fields,-f", opts.find.fields,
"Fields to display")
->default_val("d f s");
std::unordered_map<std::string, Field::Id> smap;
std::string sopts;
field_for_each([&](auto&& field){
if (field.is_searchable()) {
smap.emplace(std::string(field.name), field.id);
if (!sopts.empty())
sopts += ", ";
sopts += format("%.*s|%c",STR_V(field.name), field.shortcut);
}
});
sub.add_option("--sortfield,-s", opts.find.sortfield,
"Field to sort the results by; one of " + sopts)
->type_name("<field>")
->default_str("date")
->default_val(Field::Id::Date)
->transform(CLI::CheckedTransformer(smap));
sub.add_option("--bookmark,-b", opts.find.bookmark,
"Use bookmarked query")
->type_name("<bookmark>");
sub.add_flag("--clearlinks", opts.find.clearlinks,
"Clear old links first");
sub.add_option("--linksdir", opts.find.linksdir,
"Use bookmarked query")
->type_name("<dir>");
sub.add_option("--summary-len", opts.find.summary_len,
"Use up to so many lines for the summary")
->type_name("<lines>")
->check(CLI::PositiveNumber);
sub.add_option("--exec", opts.find.exec,
"Command to execute on message file")
->type_name("<command>");
sub.add_option("query", opts.find.query, "Search query pattern(s)")
->type_name("<query>");
}
static void
sub_help(CLI::App& sub, Options& opts)
{
sub.add_option("command", opts.help.command,
"Command to request help for")
->type_name("<command>");
}
static void
sub_index(CLI::App& sub, Options& opts)
{
sub.add_flag("--lazy-check", opts.index.lazycheck,
"Skip based on dir-timestamps");
sub.add_flag("--nocleanup", opts.index.nocleanup,
"Don't clean up database after indexing");
}
static void
sub_info(CLI::App& sub, Options& opts)
{
// nothing to do.
}
static void
sub_init(CLI::App& sub, Options& opts)
{
sub.add_option("--maildir,-m", opts.init.maildir,
"Top of the maildir")
->type_name("<maildir>");
sub.add_option("--my-address", opts.init.my_addresses,
"Personal e-mail addresses")
->type_name("<addresses>");
sub.add_option("--max-message-size", opts.init.max_msg_size,
"Maximum allowed message size in bytes");
sub.add_option("--batch-size", opts.init.max_msg_size,
"Maximum size of database transaction");
}
static void
sub_mkdir(CLI::App& sub, Options& opts)
{
sub.add_option("--mode", opts.mkdir.mode, "Set the access mode (octal)")
->default_val(0755)
->type_name("<mode>");
sub.add_option("dirs", opts.mkdir.dirs, "Path to directory/ies")
->type_name("<dir>")
->required();
}
static void
sub_remove(CLI::App& sub, Options& opts)
{
sub.add_option("files", opts.remove.files,
"Paths to message files to remove")
->type_name("<files>");
}
static void
sub_server(CLI::App& sub, Options& opts)
{
sub.add_flag("--commands", opts.server.commands,
"List available commands");
sub.add_option("--eval", opts.server.eval,
"Evaluate mu server expression")
->excludes("--commands");
}
static void
sub_verify(CLI::App& sub, Options& opts)
{
sub_crypto(sub, opts.verify);
sub.add_option("files", opts.verify.files,
"Message files to verify")
->type_name("<message-file>")
->required();
}
static void
sub_view(CLI::App& sub, Options& opts)
{
using Format = Options::View::Format;
static constexpr InfoEnum<Format, 2> FormatInfos = {{
{ Format::Plain,
{"plain", "Plain output"}
},
{ Format::Sexp,
{"sexp", "S-expressions"}
},
}};
const auto fhelp = options_help(FormatInfos, Format::Plain);
const auto fmap = options_map(FormatInfos);
sub.add_option("--format,-o", opts.view.format,
"Output format; one of " + fhelp)
->type_name("<format>")
->default_str("plain")
->default_val(Format::Plain)
->transform(CLI::CheckedTransformer(fmap));
sub_crypto(sub, opts.view);
sub.add_option("--summary-len", opts.view.summary_len,
"Use up to so many lines for the summary")
->type_name("<lines>")
->check(CLI::PositiveNumber);
sub.add_flag("--terminate", opts.view.terminate,
"Insert form-feed after each message");
sub.add_option("files", opts.view.files,
"Message files to view")
->type_name("<file>")
->required();
}
using SubCommand = Options::SubCommand;
using Category = Options::Category;
struct CommandInfo {
Category category;
std::string_view name;
std::string_view help;
// std::function is not constexp-friendly
typedef void(*setup_func_t)(CLI::App&, Options&);
setup_func_t setup_func{};
};
static constexpr
AssocPairs<SubCommand, CommandInfo, Options::SubCommandNum> SubCommandInfos= {{
{ SubCommand::Add,
{ Category::NeedsWritableStore,
"add", "Add message(s) to the database", sub_add}
},
{ SubCommand::Cfind,
{ Category::NeedsReadOnlyStore,
"cfind", "Find contacts matching pattern", sub_cfind}
},
{ SubCommand::Extract,
{Category::None,
"extract", "Extract MIME-parts from messages", sub_extract}
},
{ SubCommand::Fields,
{Category::None,
"fields", "Show a information about search fields", sub_fields}
},
{ SubCommand::Find,
{Category::NeedsReadOnlyStore,
"find", "Find messages matching query", sub_find }
},
{ SubCommand::Help,
{Category::None,
"help", "Show help information", sub_help }
},
{ SubCommand::Index,
{Category::NeedsWritableStore,
"index", "Store message information in the database", sub_index }
},
{ SubCommand::Info,
{Category::NeedsReadOnlyStore,
"info", "Show information about the message store database", sub_info }
},
{ SubCommand::Init,
{Category::NeedsWritableStore,
"init", "Initialize the database", sub_init }
},
{ SubCommand::Mkdir,
{Category::None,
"mkdir", "Create a new Maildir", sub_mkdir }
},
{ SubCommand::Remove,
{Category::NeedsWritableStore,
"remove", "Remove message from file-system and database", sub_remove }
},
{ SubCommand::Script,
// Note: SubCommand::Script is special; there's no literal
// "script" subcommand, there subcommands for all the scripts.
{Category::None,
"script", "Invoke a script", {}}
},
{ SubCommand::Server,
{Category::NeedsWritableStore,
"server", "Start a mu server (for mu4e)", sub_server}
},
{ SubCommand::Verify,
{Category::None,
"verify", "Verify cryptographic signatures", sub_verify}
},
{ SubCommand::View,
{Category::None,
"view", "View specific messages", sub_view}
},
}};
static ScriptInfos
add_scripts(CLI::App& app, Options& opts)
{
#ifndef BUILD_GUILE
return {};
#else
ScriptPaths paths = { MU_SCRIPTS_DIR };
auto scriptinfos{script_infos(paths)};
for (auto&& script: scriptinfos) {
auto&& sub = app.add_subcommand(script.name)->group("Scripts")
->description(script.oneline);
sub->add_option("params", opts.script.params,
"Parameter to script")
->type_name("<params>");
}
return scriptinfos;
#endif /*BUILD_GUILE*/
}
static Result<Options>
show_manpage(Options& opts, const std::string& name)
{
char *path = g_find_program_in_path("man");
if (!path)
return Err(Error::Code::Command,
"cannot find 'man' program");
GError* err{};
auto cmd{to_string_gchar(std::move(path)) + " " + name};
auto res = g_spawn_command_line_sync(cmd.c_str(), {}, {}, {}, &err);
if (!res)
return Err(Error::Code::Command, &err,
"error running man command");
return Ok(std::move(opts));
}
static Result<Options>
cmd_help(const CLI::App& app, Options& opts)
{
if (opts.help.command.empty()) {
std::cout << app.help() << "\n";
return Ok(std::move(opts));
}
for (auto&& item: SubCommandInfos) {
if (item.second.name == opts.help.command)
return show_manpage(opts, "mu-" + opts.help.command);
}
for (auto&& item: {"query", "easy"})
if (item == opts.help.command)
return show_manpage(opts, "mu-" + opts.help.command);
return Err(Error::Code::Command,
"no help available for '%s'", opts.help.command.c_str());
}
static void
add_global_options(CLI::App& cli, Options& opts)
{
static const auto default_no_color =
!::isatty(::fileno(stdout)) ||
!::isatty(::fileno(stderr)) ||
::getenv("NO_COLOR") != NULL;
errno = 0;
cli.add_flag("-q,--quiet", opts.quiet, "Hide non-essential output");
cli.add_flag("-v,--verbose", opts.verbose, "Show verbose output");
cli.add_flag("--log-stderr", opts.log_stderr, "Log to stderr");
cli.add_flag("--nocolor", opts.nocolor, "Don't show ANSI colors")
->default_val(default_no_color)
->default_str(default_no_color ? "<true>" : "<false>");
cli.add_flag("-d,--debug", opts.debug, "Run in debug mode")
->group(""/*always hide*/);
}
Result<Options>
Options::make(int argc, char *argv[])
{
Options opts{};
CLI::App app{"mu mail indexer/searcher", "mu"};
app.description(R"(mu mail indexer/searcher
Copyright (C) 2008-2022 Dirk-Jan C. Binnema
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
)");
app.set_version_flag("-V,--version", PACKAGE_VERSION);
app.set_help_flag("-h,--help", "Show help informmation");
app.set_help_all_flag("--help-all");
app.require_subcommand(0, 1);
add_global_options(app, opts);
/*
* subcommands
*
* we keep around a map of the subcommand pointers, so we can
* easily find the chosen one (if any) later.
*/
for (auto&& cmdinfo: SubCommandInfos) {
//const auto cmdtype = cmdinfo.first;
const auto name{std::string{cmdinfo.second.name}};
const auto help{std::string{cmdinfo.second.help}};
const auto setup{cmdinfo.second.setup_func};
const auto cat{category(cmdinfo.first)};
if (!setup)
continue;
auto sub = app.add_subcommand(name, help);
setup(*sub, opts);
/* allow global options _after_ subcommand as well;
* this is for backward compat with the older
* command-line parsing */
sub->fallthrough(true);
/* store commands get the '--muhome' parameter as well */
if (cat == Category::NeedsReadOnlyStore ||
cat == Category::NeedsWritableStore)
sub->add_option("--muhome",
opts.muhome, "Specify alternative mu directory")
->envname("MUHOME")
->type_name("<dir>");
}
/* add scripts (if supported) as semi-subscommands as well */
const auto scripts = add_scripts(app, opts);
try {
app.parse(argc, argv);
// find the chosen sub command, if any.
for (auto&& cmdinfo: SubCommandInfos) {
if (cmdinfo.first == SubCommand::Script)
continue; // not a _real_ subcommand.
const auto name{std::string{cmdinfo.second.name}};
if (app.got_subcommand(name)) {
opts.sub_command = cmdinfo.first;
}
}
// otherwise, perhaps it's a script?
if (!opts.sub_command) {
for (auto&& info: scripts) { // find the chosen script, if any.
if (app.got_subcommand(info.name)) {
opts.sub_command = SubCommand::Script;
opts.script.name = info.name;
}
}
}
// if nothing else, try "help"
if (opts.sub_command.value_or(SubCommand::Help) == SubCommand::Help)
return cmd_help(app, opts);
} catch (const CLI::CallForHelp& cfh) {
std::cout << app.help() << std::flush;
} catch (const CLI::CallForAllHelp& cfah) {
std::cout << app.help("", CLI::AppFormatMode::All) << std::flush;
} catch (const CLI::CallForVersion&) {
std::cout << "version " << PACKAGE_VERSION << "\n";
} catch (const CLI::ParseError& pe) {
return Err(Error::Code::InvalidArgument, "%s", pe.what());
} catch (...) {
return Err(Error::Code::Internal, "error parsing arguments");
}
return Ok(std::move(opts));
}
Category
Options::category(Options::SubCommand sub)
{
for (auto&& item: SubCommandInfos)
if (item.first == sub)
return item.second.category;
return Category::None;
}
/*
* trust but verify
*/
static constexpr bool
validate_subcommand_ids()
{
for (auto u = 0U; u != SubCommandInfos.size(); ++u)
if (static_cast<size_t>(SubCommandInfos.at(u).first) != u)
return false;
return true;
}
/*
* tests... also build as runtime-tests, so we can get coverage info
*/
#ifdef BUILD_TESTS
#define static_assert g_assert_true
#endif /*BUILD_TESTS*/
[[maybe_unused]]
static void
test_ids()
{
static_assert(validate_subcommand_ids());
}
#ifdef BUILD_TESTS
int
main(int argc, char* argv[])
{
mu_test_init(&argc, &argv);
g_test_add_func("/options/ids", test_ids);
return g_test_run();
}
#endif /*BUILD_TESTS*/

278
mu/mu-options.hh Normal file
View File

@ -0,0 +1,278 @@
/*
** Copyright (C) 2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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_OPTIONS_HH__
#define MU_OPTIONS_HH__
#include <sstream>
#include <string>
#include <vector>
#include <utils/mu-option.hh>
#include <utils/mu-result.hh>
#include <utils/mu-utils.hh>
#include <message/mu-fields.hh>
#include <mu-script.hh>
#include <ctime>
#include <sys/stat.h>
/* command-line options for Mu */
namespace Mu {
struct Options {
using OptSize = Option<std::size_t>;
using SizeVec = std::vector<std::size_t>;
using OptTStamp = Option<std::time_t>;
using OptFieldId = Option<Field::Id>;
using StringVec = std::vector<std::string>;
/*
* general options
*/
bool quiet; /**< don't give any output */
bool debug; /**< log debug-level info */
bool version; /**< request mu version */
bool log_stderr; /**< log to stderr */
bool nocolor; /**< don't use use ansi-colors */
bool verbose; /**< verbose output */
std::string muhome; /**< alternative mu dir */
enum struct SubCommand {
Add, Cfind, Extract, Fields, Find, Help, Index,Info, Init, Mkdir,
Remove, Script, Server, Verify, View/*must be last*/
};
static constexpr std::size_t SubCommandNum =
1 + static_cast<std::size_t>(SubCommand::View);
static constexpr std::array<SubCommand, SubCommandNum> SubCommands = {{
SubCommand::Add,
SubCommand::Cfind,
SubCommand::Extract,
SubCommand::Fields,
SubCommand::Find,
SubCommand::Help,
SubCommand::Index,
SubCommand::Info,
SubCommand::Init,
SubCommand::Mkdir,
SubCommand::Remove,
SubCommand::Script,
SubCommand::Server,
SubCommand::Verify,
SubCommand::View
}};
Option<SubCommand> sub_command; /**< The chosen sub-command, if any. */
/*
* Add
*/
struct Add {
StringVec files; /**< field to add */
} add;
/*
* Cfind
*/
struct Cfind {
enum struct Format { Plain, MuttAlias, MuttAddressBook,
Wanderlust, OrgContact, Bbdb, Csv, Debug };
Format format; /**< Output format */
bool personal; /**< only show personal contacts */
OptTStamp after; /**< only last seen after tstamp */
OptSize maxnum; /**< maximum number of results */
std::string rx_pattern; /**< contact regexp to match */
} cfind;
struct Crypto {
bool auto_retrieve; /**< auto-retrieve keys */
bool decrypt; /**< decrypt */
};
/*
* Extract
*/
struct Extract: public Crypto {
std::string message; /**< path to message file */
bool save_all; /**< extract all parts */
bool save_attachments; /**< extract all attachment parts */
SizeVec parts; /**< parts to save / open */
std::string targetdir{}; /**< where to save attachments */
bool overwrite; /**< overwrite same-named files */
bool play; /**< try to 'play' attachment */
std::string filename_rx; /**< Filename rx to save */
} extract;
/*
* Fields
*/
/*
* Find
*/
struct Find {
std::string fields; /**< fields to show in output */
Field::Id sortfield; /**< field to sort by */
OptSize maxnum; /**< max # of entries to print */
bool reverse; /**< sort in revers order (z->a) */
bool threads; /**< show message threads */
bool clearlinks; /**< clear linksdir first */
std::string linksdir; /**< directory for links */
OptSize summary_len; /**< max # of lines for summary */
std::string bookmark; /**< use bookmark */
enum struct Format { Plain, Links, Xml, Json, Sexp, XQuery, MQuery, Exec };
Format format; /**< Output format */
std::string exec; /**< cmd to execute on matches */
bool skip_dups; /**< show only first with msg id */
bool include_related; /**< included related messages */
/**< for find and cind */
OptTStamp after; /**< only last seen after T */
bool auto_retrieve; /**< assume we're online */
bool decrypt; /**< try to decrypt the body */
StringVec query; /**< search query */
} find;
struct Help {
std::string command; /**< Help parameter */
} help;
/*
* Index
*/
struct Index {
bool nocleanup; /**< don't cleanup del'd mails */
bool lazycheck; /**< don't check uptodate dirs */
} index;
/*
* Info
*/
/*
* Init
*/
struct Init {
std::string maildir; /**< where the mails are */
StringVec my_addresses; /**< personal e-mail addresses */
OptSize max_msg_size; /**< max size for message files */
OptSize batch_size; /**< db transaction batch size */
bool reinit; /**< re-initialize */
} init;
/*
* Mkdir
*/
struct Mkdir {
StringVec dirs; /**< Dir(s) to create */
mode_t mode; /**< Mode for the maildir */
} mkdir;
/*
* Remove
*/
struct Remove {
StringVec files; /**< Files to remove */
} remove;
/*
* Scripts (i.e., finding scriot)
*/
struct Script {
std::string name; /**< name of script */
StringVec params; /**< script params */
} script;
/*
* Server
*/
struct Server {
bool commands; /**< dump docs for commands */
std::string eval; /**< command to evaluate */
} server;
/*
* Verify
*/
struct Verify: public Crypto {
StringVec files; /**< message files to verify */
} verify;
/*
* View
*/
struct View: public Crypto {
bool terminate; /**< add \f between msgs in view */
OptSize summary_len; /**< max # of lines for summary */
enum struct Format { Plain, Sexp };
Format format; /**< output format*/
StringVec files; /**< Message file(s) */
} view;
/**
* Create an Options structure fo the given command-line arguments.
*
* @param argc argc
* @param argv argc
*
* @return Options, or an Error
*/
static Result<Options> make(int argc, char *argv[]);
/**
* Different commands need different things
*
*/
enum struct Category {
None,
NeedsReadOnlyStore,
NeedsWritableStore,
};
/**
* Get the category for some subcommand
*
* @param sub subcommand
*
* @return the category
*/
static Category category(SubCommand sub);
/**
* Get some well-known Path
*
* @param path the Path to find
*
* @return the path name
*/
std::string runtime_path(RuntimePath path) const {
return Mu::runtime_path(path, muhome);
}
};
} // namepace Mu
#endif /* MU_OPTIONS_HH__ */

View File

@ -18,40 +18,29 @@
*/
#include <config.h>
#include <functional>
#include <glib.h>
#include <glib-object.h>
#include <locale.h>
#include "mu-config.hh"
#include "mu-cmd.hh"
#include "mu-runtime.hh"
#include "mu-options.hh"
#include "utils/mu-utils.hh"
#include "utils/mu-logger.hh"
#include "mu-cmd.hh"
using namespace Mu;
static void
show_version(void)
{
const char* blurb = "mu (mail indexer/searcher) version " VERSION "\n"
"Copyright (C) 2008-2022 Dirk-Jan C. Binnema\n"
"License GPLv3+: GNU GPL version 3 or later "
"<http://gnu.org/licenses/gpl.html>.\n"
"This is free software: you are free to change "
"and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.";
g_print("%s\n", blurb);
}
static int
handle_result(const Result<void>& res, MuConfig* conf)
handle_result(const Result<void>& res, const Mu::Options& opts)
{
if (res)
return 0;
using Color = MaybeAnsi::Color;
MaybeAnsi col{conf ? !conf->nocolor : false};
MaybeAnsi col{!opts.nocolor};
// show the error and some help, but not if it's only a softerror.
if (!res.error().is_soft_error()) {
@ -66,8 +55,6 @@ handle_result(const Result<void>& res, MuConfig* conf)
// perhaps give some useful hint on how to solve it.
switch (res.error().code()) {
case Error::Code::InvalidArgument:
if (conf && mu_config_cmd_is_valid(conf->cmd))
mu_config_show_help(conf->cmd);
break;
case Error::Code::StoreLock:
std::cerr << "Perhaps mu is already running?\n";
@ -88,38 +75,38 @@ handle_result(const Result<void>& res, MuConfig* conf)
int
main(int argc, char* argv[])
{
int rv{};
MuConfig *conf{};
GError* err{};
using Color = MaybeAnsi::Color;
MaybeAnsi col{conf ? !conf->nocolor : false};
setlocale(LC_ALL, "");
conf = mu_config_init(&argc, &argv, &err);
if (!conf) {
std::cerr << col.fg(Color::Red) << "error" << col.reset() << ": "
<< col.fg(Color::BrightYellow)
<< (err ? err->message : "something went wrong") << "\n";
rv = 1;
goto cleanup;
} else if (conf->version) {
show_version();
goto cleanup;
} else if (conf->cmd == MU_CONFIG_CMD_NONE) /* nothing to do */
goto cleanup;
else if (!mu_runtime_init(conf->muhome, PACKAGE_NAME, conf->debug)) {
std::cerr << col.fg(Color::Red) << "error initializing mu\n"
<< col.reset();
rv = 2;
} else
rv = handle_result(mu_cmd_execute(conf), conf);
/*
* read command-line options
*/
const auto opts{Options::make(argc, argv)};
if (!opts) {
std::cerr << "error: " << opts.error().what() << "\n";
return opts.error().exit_code();
} else if (!opts->sub_command) {
// nothing more to do.
return 0;
}
cleanup:
g_clear_error(&err);
mu_config_uninit(conf);
mu_runtime_uninit();
/*
* set up logging
*/
Logger::Options lopts{Logger::Options::None};
if (opts->log_stderr)
lopts |= Logger::Options::StdOutErr;
if (opts->debug)
lopts |= Logger::Options::Debug;
return rv;
const auto logger = Logger::make(opts->runtime_path(RuntimePath::LogFile),
lopts);
if (!logger) {
std::cerr << "error:" << logger.error().what() << "\n";
return logger.error().exit_code();
}
/*
* handle sub command
*/
return handle_result(mu_cmd_execute(*opts), *opts);
}