mirror of https://github.com/djcb/mu.git
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:
parent
27a474be41
commit
36f6e387ae
|
@ -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') + '"'],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
272
mu/mu-cmd.cc
272
mu/mu-cmd.cc
|
@ -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:
|
||||
|
|
46
mu/mu-cmd.hh
46
mu/mu-cmd.hh
|
@ -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
|
||||
|
||||
|
|
738
mu/mu-config.cc
738
mu/mu-config.cc
|
@ -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;
|
||||
}
|
246
mu/mu-config.hh
246
mu/mu-config.hh
|
@ -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__*/
|
|
@ -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 */"
|
||||
}
|
|
@ -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
|
|
@ -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*/
|
|
@ -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__ */
|
87
mu/mu.cc
87
mu/mu.cc
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue