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:
@ -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
|
## 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
|
## 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,
|
## along with this program; if not, write to the Free Software Foundation,
|
||||||
## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
## 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 = executable(
|
||||||
'mu', [
|
'mu', [
|
||||||
'mu.cc',
|
'mu.cc',
|
||||||
|
'mu-options.cc',
|
||||||
'mu-cmd-cfind.cc',
|
'mu-cmd-cfind.cc',
|
||||||
'mu-cmd-extract.cc',
|
'mu-cmd-extract.cc',
|
||||||
'mu-cmd-fields.cc',
|
'mu-cmd-fields.cc',
|
||||||
@ -30,11 +25,7 @@ mu = executable(
|
|||||||
'mu-cmd-index.cc',
|
'mu-cmd-index.cc',
|
||||||
'mu-cmd-script.cc',
|
'mu-cmd-script.cc',
|
||||||
'mu-cmd-server.cc',
|
'mu-cmd-server.cc',
|
||||||
'mu-cmd.cc',
|
'mu-cmd.cc'
|
||||||
'mu-cmd.hh',
|
|
||||||
'mu-config.cc',
|
|
||||||
'mu-config.hh',
|
|
||||||
mu_help_strings_h
|
|
||||||
],
|
],
|
||||||
dependencies: [ glib_dep, gmime_dep, lib_mu_dep, thread_dep, config_h_dep ],
|
dependencies: [ glib_dep, gmime_dep, lib_mu_dep, thread_dep, config_h_dep ],
|
||||||
cpp_args: ['-DMU_SCRIPTS_DIR="'+ join_paths(datadir, 'mu', 'scripts') + '"'],
|
cpp_args: ['-DMU_SCRIPTS_DIR="'+ join_paths(datadir, 'mu', 'scripts') + '"'],
|
||||||
|
|||||||
@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-contacts-cache.hh"
|
#include "mu-contacts-cache.hh"
|
||||||
#include "mu-runtime.hh"
|
|
||||||
|
|
||||||
#include "utils/mu-util.h"
|
#include "utils/mu-util.h"
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
@ -35,6 +34,7 @@
|
|||||||
|
|
||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* macro to check whether the string is empty, ie. if it's NULL or
|
* macro to check whether the string is empty, ie. if it's NULL or
|
||||||
* it's length is 0
|
* it's length is 0
|
||||||
@ -187,15 +187,17 @@ leave : {
|
|||||||
return nick;
|
return nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using Format = Options::Cfind::Format;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
print_header(const MuConfigFormat format)
|
print_header(Format format)
|
||||||
{
|
{
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case MU_CONFIG_FORMAT_BBDB:
|
case Format::Bbdb:
|
||||||
g_print(";; -*-coding: utf-8-emacs;-*-\n"
|
g_print(";; -*-coding: utf-8-emacs;-*-\n"
|
||||||
";;; file-version: 6\n");
|
";;; file-version: 6\n");
|
||||||
break;
|
break;
|
||||||
case MU_CONFIG_FORMAT_MUTT_AB:
|
case Format::MuttAddressBook:
|
||||||
g_print("Matching addresses in the mu database:\n");
|
g_print("Matching addresses in the mu database:\n");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -275,7 +277,7 @@ print_plain(const std::string& email, const std::string& name, bool color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ECData {
|
struct ECData {
|
||||||
MuConfigFormat format;
|
Format format;
|
||||||
gboolean color, personal;
|
gboolean color, personal;
|
||||||
time_t after;
|
time_t after;
|
||||||
GRegex* rx;
|
GRegex* rx;
|
||||||
@ -304,28 +306,30 @@ each_contact(const Mu::Contact& ci, ECData& ecdata)
|
|||||||
++ecdata.n;
|
++ecdata.n;
|
||||||
|
|
||||||
switch (ecdata.format) {
|
switch (ecdata.format) {
|
||||||
case MU_CONFIG_FORMAT_MUTT_ALIAS:
|
case Format::MuttAlias:
|
||||||
each_contact_mutt_alias(ci.email, ci.name, ecdata.nicks);
|
each_contact_mutt_alias(ci.email, ci.name, ecdata.nicks);
|
||||||
break;
|
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());
|
mu_util_print_encoded("%s\t%s\t\n", ci.email.c_str(), ci.name.c_str());
|
||||||
break;
|
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;
|
break;
|
||||||
case MU_CONFIG_FORMAT_ORG_CONTACT:
|
case Format::OrgContact:
|
||||||
if (!ci.name.empty())
|
if (!ci.name.empty())
|
||||||
mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n",
|
mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n",
|
||||||
ci.name.c_str(),
|
ci.name.c_str(),
|
||||||
ci.email.c_str());
|
ci.email.c_str());
|
||||||
break;
|
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;
|
break;
|
||||||
case MU_CONFIG_FORMAT_CSV:
|
case Format::Csv:
|
||||||
mu_util_print_encoded("%s,%s\n",
|
mu_util_print_encoded("%s,%s\n",
|
||||||
ci.name.empty() ? "" : Mu::quote(ci.name).c_str(),
|
ci.name.empty() ? "" : Mu::quote(ci.name).c_str(),
|
||||||
Mu::quote(ci.email).c_str());
|
Mu::quote(ci.email).c_str());
|
||||||
break;
|
break;
|
||||||
case MU_CONFIG_FORMAT_DEBUG: {
|
case Format::Debug: {
|
||||||
char datebuf[32];
|
char datebuf[32];
|
||||||
const auto mdate(static_cast<::time_t>(ci.message_date));
|
const auto mdate(static_cast<::time_t>(ci.message_date));
|
||||||
::strftime(datebuf, sizeof(datebuf), "%F %T", ::gmtime(&mdate));
|
::strftime(datebuf, sizeof(datebuf), "%F %T", ::gmtime(&mdate));
|
||||||
@ -346,21 +350,21 @@ each_contact(const Mu::Contact& ci, ECData& ecdata)
|
|||||||
|
|
||||||
static Result<void>
|
static Result<void>
|
||||||
run_cmd_cfind(const Mu::Store& store,
|
run_cmd_cfind(const Mu::Store& store,
|
||||||
const char* pattern,
|
const std::string& pattern,
|
||||||
gboolean personal,
|
bool personal,
|
||||||
time_t after,
|
time_t after,
|
||||||
int maxnum,
|
size_t maxnum,
|
||||||
const MuConfigFormat format,
|
Format format,
|
||||||
gboolean color)
|
bool color)
|
||||||
{
|
{
|
||||||
ECData ecdata{};
|
ECData ecdata{};
|
||||||
GError *err{};
|
GError *err{};
|
||||||
|
|
||||||
memset(&ecdata, 0, sizeof(ecdata));
|
memset(&ecdata, 0, sizeof(ecdata));
|
||||||
|
|
||||||
if (pattern) {
|
if (!pattern.empty()) {
|
||||||
ecdata.rx = g_regex_new(
|
ecdata.rx = g_regex_new(
|
||||||
pattern,
|
pattern.c_str(),
|
||||||
(GRegexCompileFlags)(G_REGEX_CASELESS | G_REGEX_OPTIMIZE),
|
(GRegexCompileFlags)(G_REGEX_CASELESS | G_REGEX_OPTIMIZE),
|
||||||
(GRegexMatchFlags)0, &err);
|
(GRegexMatchFlags)0, &err);
|
||||||
|
|
||||||
@ -393,47 +397,14 @@ run_cmd_cfind(const Mu::Store& store,
|
|||||||
return Ok();
|
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>
|
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,
|
return run_cmd_cfind(store,
|
||||||
opts->params[1],
|
opts.cfind.rx_pattern,
|
||||||
opts->personal,
|
opts.cfind.personal,
|
||||||
opts->after,
|
opts.cfind.after.value_or(0),
|
||||||
opts->maxnum,
|
opts.cfind.maxnum.value_or(0),
|
||||||
opts->format,
|
opts.cfind.format,
|
||||||
!opts->nocolor);
|
!opts.nocolor);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-config.hh"
|
|
||||||
#include "utils/mu-util.h"
|
#include "utils/mu-util.h"
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
#include <message/mu-message.hh>
|
#include <message/mu-message.hh>
|
||||||
@ -29,19 +28,19 @@ using namespace Mu;
|
|||||||
|
|
||||||
|
|
||||||
static Result<void>
|
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([&]{
|
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;
|
return tdir.empty() ? tdir : tdir + G_DIR_SEPARATOR_S;
|
||||||
});
|
});
|
||||||
const auto path{targetdir +
|
const auto path{targetdir +
|
||||||
part.cooked_filename().value_or(format("part-%zu", idx))};
|
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());
|
return Err(res.error());
|
||||||
|
|
||||||
if (opts->play) {
|
if (opts.extract.play) {
|
||||||
GError *err{};
|
GError *err{};
|
||||||
if (auto res{mu_util_play(path.c_str(), &err)};
|
if (auto res{mu_util_play(path.c_str(), &err)};
|
||||||
res != MU_OK)
|
res != MU_OK)
|
||||||
@ -53,39 +52,37 @@ save_part(const Message::Part& part, size_t idx, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
static Result<void>
|
||||||
save_parts(const std::string& path, Option<std::string>& filename_rx,
|
save_parts(const std::string& path, const std::string& filename_rx,
|
||||||
const MuConfig* opts)
|
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)
|
if (!message)
|
||||||
return Err(std::move(message.error()));
|
return Err(std::move(message.error()));
|
||||||
|
|
||||||
|
|
||||||
size_t partnum{}, saved_num{};
|
size_t partnum{}, saved_num{};
|
||||||
const auto partnums = std::invoke([&]()->std::vector<size_t> {
|
for (auto&& part: message->parts()) {
|
||||||
std::vector<size_t> nums;
|
++partnum;
|
||||||
for (auto&& numstr : split(opts->parts ? opts->parts : "", ','))
|
// should we extract this part?
|
||||||
nums.emplace_back(
|
const auto do_extract = std::invoke([&]() {
|
||||||
static_cast<size_t>(::atoi(numstr.c_str())));
|
|
||||||
return nums;
|
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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!do_extract)
|
||||||
for (auto&& part: message->parts()) {
|
continue;
|
||||||
|
|
||||||
++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 (auto res = save_part(part, partnum, opts); !res)
|
if (auto res = save_part(part, partnum, opts); !res)
|
||||||
return res;
|
return res;
|
||||||
@ -93,11 +90,11 @@ save_parts(const std::string& path, Option<std::string>& filename_rx,
|
|||||||
++saved_num;
|
++saved_num;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (saved_num == 0)
|
if (saved_num == 0)
|
||||||
// return Err(Error::Code::File,
|
return Err(Error::Code::File,
|
||||||
// "no %s extracted from this message",
|
"no %s extracted from this message",
|
||||||
// opts->save_attachments ? "attachments" : "parts");
|
opts.extract.save_attachments ? "attachments" : "parts");
|
||||||
// else
|
else
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,66 +137,32 @@ show_part(const MessagePart& part, size_t index, bool color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Mu::Result<void>
|
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, message_options(opts.extract))};
|
||||||
|
|
||||||
auto msg_res{Message::make_from_path(path, mu_config_message_options(opts))};
|
|
||||||
if (!msg_res)
|
if (!msg_res)
|
||||||
return Err(std::move(msg_res.error()));
|
return Err(std::move(msg_res.error()));
|
||||||
|
|
||||||
/* TODO: update this for crypto */
|
|
||||||
size_t index{};
|
size_t index{};
|
||||||
g_print("MIME-parts in this message:\n");
|
g_print("MIME-parts in this message:\n");
|
||||||
for (auto&& part: msg_res->parts())
|
for (auto&& part: msg_res->parts())
|
||||||
show_part(part, ++index, !opts->nocolor);
|
show_part(part, ++index, !opts.nocolor);
|
||||||
|
|
||||||
return Ok();
|
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::Result<void>
|
||||||
Mu::mu_cmd_extract(const MuConfig* opts)
|
Mu::mu_cmd_extract(const Options& opts)
|
||||||
{
|
{
|
||||||
if (!opts || opts->cmd != MU_CONFIG_CMD_EXTRACT)
|
if (opts.extract.parts.empty() &&
|
||||||
return Err(Error::Code::Internal, "error in arguments");
|
!opts.extract.save_attachments && !opts.extract.save_all &&
|
||||||
if (auto res = check_params(opts); !res)
|
opts.extract.filename_rx.empty())
|
||||||
return Err(std::move(res.error()));
|
return show_parts(opts.extract.message, opts); /* show, don't save */
|
||||||
|
|
||||||
if (!opts->params[2] && !opts->parts &&
|
if (!mu_util_check_dir(opts.extract.targetdir.c_str(), FALSE, TRUE))
|
||||||
!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))
|
|
||||||
return Err(Error::Code::File,
|
return Err(Error::Code::File,
|
||||||
"target '%s' is not a writable directory",
|
"target '%s' is not a writable directory",
|
||||||
opts->targetdir);
|
opts.extract.targetdir.c_str());
|
||||||
|
|
||||||
Option<std::string> pattern{};
|
return save_parts(opts.extract.message, opts.extract.filename_rx, opts);
|
||||||
if (opts->params[2])
|
|
||||||
pattern = opts->params[2];
|
|
||||||
|
|
||||||
return save_parts(opts->params[1], pattern, opts);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,9 +30,9 @@ using namespace tabulate;
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
table_header(Table& table, const MuConfig* opts)
|
table_header(Table& table, const Options& opts)
|
||||||
{
|
{
|
||||||
if (opts->nocolor)
|
if (opts.nocolor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(*table.begin()).format()
|
(*table.begin()).format()
|
||||||
@ -42,7 +42,7 @@ table_header(Table& table, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
show_fields(const MuConfig* opts)
|
show_fields(const Options& opts)
|
||||||
{
|
{
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ show_fields(const MuConfig* opts)
|
|||||||
if (sv.empty())
|
if (sv.empty())
|
||||||
return "";
|
return "";
|
||||||
else
|
else
|
||||||
return format("%*s", STR_V(sv));
|
return format("%.*s", STR_V(sv));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto searchable=[&](const Field& field)->std::string {
|
auto searchable=[&](const Field& field)->std::string {
|
||||||
@ -76,8 +76,8 @@ show_fields(const MuConfig* opts)
|
|||||||
if (field.is_internal())
|
if (field.is_internal())
|
||||||
return; // skip.
|
return; // skip.
|
||||||
|
|
||||||
fields.add_row({format("%*s", STR_V(field.name)),
|
fields.add_row({format("%.*s", STR_V(field.name)),
|
||||||
field.alias.empty() ? "" : format("%*s", STR_V(field.alias)),
|
field.alias.empty() ? "" : format("%.*s", STR_V(field.alias)),
|
||||||
field.shortcut ? format("%c", field.shortcut) : ""s,
|
field.shortcut ? format("%c", field.shortcut) : ""s,
|
||||||
searchable(field),
|
searchable(field),
|
||||||
field.is_value() ? "yes" : "no",
|
field.is_value() ? "yes" : "no",
|
||||||
@ -93,7 +93,7 @@ show_fields(const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
show_flags(const MuConfig* opts)
|
show_flags(const Options& opts)
|
||||||
{
|
{
|
||||||
using namespace tabulate;
|
using namespace tabulate;
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
@ -119,7 +119,7 @@ show_flags(const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
}, info.category);
|
}, info.category);
|
||||||
|
|
||||||
flags.add_row({format("%*s", STR_V(info.name)),
|
flags.add_row({format("%.*s", STR_V(info.name)),
|
||||||
format("%c", info.shortcut),
|
format("%c", info.shortcut),
|
||||||
catname,
|
catname,
|
||||||
std::string{info.description}});
|
std::string{info.description}});
|
||||||
@ -133,14 +133,11 @@ show_flags(const MuConfig* opts)
|
|||||||
|
|
||||||
|
|
||||||
Result<void>
|
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())
|
if (!locale_workaround())
|
||||||
return Err(Error::Code::User, "failed to find a working locale");
|
return Err(Error::Code::User, "failed to find a working locale");
|
||||||
|
|
||||||
|
|
||||||
std::cout << "#\n# message fields\n#\n";
|
std::cout << "#\n# message fields\n#\n";
|
||||||
show_fields(opts);
|
show_fields(opts);
|
||||||
std::cout << "\n#\n# message flags\n#\n";
|
std::cout << "\n#\n# message flags\n#\n";
|
||||||
|
|||||||
@ -33,7 +33,6 @@
|
|||||||
#include "mu-query-match-deciders.hh"
|
#include "mu-query-match-deciders.hh"
|
||||||
#include "mu-query.hh"
|
#include "mu-query.hh"
|
||||||
#include "mu-bookmarks.hh"
|
#include "mu-bookmarks.hh"
|
||||||
#include "mu-runtime.hh"
|
|
||||||
#include "message/mu-message.hh"
|
#include "message/mu-message.hh"
|
||||||
|
|
||||||
#include "utils/mu-option.hh"
|
#include "utils/mu-option.hh"
|
||||||
@ -44,6 +43,8 @@
|
|||||||
|
|
||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
|
|
||||||
|
using Format = Options::Find::Format;
|
||||||
|
|
||||||
struct OutputInfo {
|
struct OutputInfo {
|
||||||
Xapian::docid docid{};
|
Xapian::docid docid{};
|
||||||
bool header{};
|
bool header{};
|
||||||
@ -56,52 +57,50 @@ constexpr auto FirstOutput{OutputInfo{0, true, false, {}, {}}};
|
|||||||
constexpr auto LastOutput{OutputInfo{0, false, true, {}, {}}};
|
constexpr auto LastOutput{OutputInfo{0, false, true, {}, {}}};
|
||||||
|
|
||||||
using OutputFunc = std::function<bool(const Option<Message>& msg, const OutputInfo&,
|
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>
|
static Result<void>
|
||||||
print_internal(const Store& store,
|
print_internal(const Store& store,
|
||||||
const std::string& expr,
|
const std::string& expr,
|
||||||
gboolean xapian,
|
bool xapian,
|
||||||
gboolean warn)
|
bool warn)
|
||||||
{
|
{
|
||||||
std::cout << store.parse_query(expr, xapian) << "\n";
|
std::cout << store.parse_query(expr, xapian) << "\n";
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<QueryResults>
|
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};
|
Mu::QueryFlags qflags{QueryFlags::SkipUnreadable};
|
||||||
if (opts->reverse)
|
if (opts.find.reverse)
|
||||||
qflags |= QueryFlags::Descending;
|
qflags |= QueryFlags::Descending;
|
||||||
if (opts->skip_dups)
|
if (opts.find.skip_dups)
|
||||||
qflags |= QueryFlags::SkipDuplicates;
|
qflags |= QueryFlags::SkipDuplicates;
|
||||||
if (opts->include_related)
|
if (opts.find.include_related)
|
||||||
qflags |= QueryFlags::IncludeRelated;
|
qflags |= QueryFlags::IncludeRelated;
|
||||||
if (opts->threads)
|
if (opts.find.threads)
|
||||||
qflags |= QueryFlags::Threading;
|
qflags |= QueryFlags::Threading;
|
||||||
|
|
||||||
return store.run_query(expr, sortfield.value_or(field_from_id(Field::Id::Date)).id,
|
return store.run_query(expr,
|
||||||
qflags, opts->maxnum);
|
opts.find.sortfield,
|
||||||
|
qflags, opts.find.maxnum.value_or(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static bool
|
||||||
exec_cmd(const Option<Message>& msg, const OutputInfo& info, const MuConfig* opts, GError** err)
|
exec_cmd(const Option<Message>& msg, const OutputInfo& info, const Options& opts, GError** err)
|
||||||
{
|
{
|
||||||
if (!msg)
|
if (!msg)
|
||||||
return TRUE;
|
return true;
|
||||||
|
|
||||||
gint status;
|
gint status;
|
||||||
char * cmdline, *escpath;
|
char * cmdline, *escpath;
|
||||||
gboolean rv;
|
bool rv;
|
||||||
|
|
||||||
escpath = g_shell_quote(msg->path().c_str());
|
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);
|
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;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gchar*
|
static Result<std::string>
|
||||||
resolve_bookmark(const MuConfig* opts, GError** err)
|
resolve_bookmark(const Options& opts)
|
||||||
{
|
{
|
||||||
MuBookmarks* bm;
|
const auto bmfile = opts.runtime_path(RuntimePath::Bookmarks);
|
||||||
char* val;
|
auto bm = mu_bookmarks_new(bmfile.c_str());
|
||||||
const gchar* bmfile;
|
if (!bm)
|
||||||
|
return Err(Error::Code::File,
|
||||||
|
"failed to open bookmarks file '%s'", bmfile.c_str());
|
||||||
|
|
||||||
bmfile = mu_runtime_path(MU_RUNTIME_PATH_BOOKMARKS);
|
const auto bookmark{opts.find.bookmark};
|
||||||
bm = mu_bookmarks_new(bmfile);
|
const auto val = mu_bookmarks_lookup(bm, bookmark.c_str());
|
||||||
if (!bm) {
|
if (!val) {
|
||||||
g_set_error(err,
|
mu_bookmarks_destroy(bm);
|
||||||
MU_ERROR_DOMAIN,
|
return Err(Error::Code::NoMatches,
|
||||||
MU_ERROR_FILE_CANNOT_OPEN,
|
"bookmark '%s' not found", bookmark.c_str());
|
||||||
"failed to open bookmarks file '%s'",
|
|
||||||
bmfile);
|
|
||||||
return FALSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
mu_bookmarks_destroy(bm);
|
||||||
return val;
|
return Ok(std::string(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<std::string>
|
static Result<std::string>
|
||||||
get_query(const MuConfig* opts)
|
get_query(const Options& opts)
|
||||||
{
|
{
|
||||||
GError *err{};
|
if (opts.find.bookmark.empty() && opts.find.query.empty())
|
||||||
gchar *query, *bookmarkval;
|
return Err(Error::Code::InvalidArgument,
|
||||||
|
"neither bookmark nor query");
|
||||||
|
|
||||||
/* params[0] is 'find', actual search params start with [1] */
|
std::string bookmark;
|
||||||
if (!opts->bookmark && !opts->params[1])
|
if (!opts.find.bookmark.empty()) {
|
||||||
return Err(Error::Code::InvalidArgument, "error in parameters");
|
const auto res = resolve_bookmark(opts);
|
||||||
|
if (!res)
|
||||||
bookmarkval = {};
|
return Err(std::move(res.error()));
|
||||||
if (opts->bookmark) {
|
bookmark = res.value() + " ";
|
||||||
bookmarkval = resolve_bookmark(opts, &err);
|
|
||||||
if (!bookmarkval)
|
|
||||||
return Err(Error::Code::Command, &err,
|
|
||||||
"failed to resolve bookmark");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query = g_strjoinv(" ", &opts->params[1]);
|
auto&& query{join(opts.find.query, " ")};
|
||||||
if (bookmarkval) {
|
return Ok(bookmark + query);
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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
|
/* note, mu_maildir_mkdir simply ignores whatever part of the
|
||||||
* mail dir already exists */
|
* 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);
|
res.error().fill_g_error(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts->clearlinks)
|
if (!opts.find.clearlinks)
|
||||||
return false;
|
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);
|
res.error().fill_g_error(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -195,14 +172,14 @@ prepare_links(const MuConfig* opts, GError** err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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)
|
if (info.header)
|
||||||
return prepare_links(opts, err);
|
return prepare_links(opts, err);
|
||||||
else if (info.footer)
|
else if (info.footer)
|
||||||
return true;
|
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);
|
res.error().fill_g_error(err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -211,7 +188,7 @@ output_link(const Option<Message>& msg, const OutputInfo& info, const MuConfig*
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ansi_color_maybe(Field::Id field_id, gboolean color)
|
ansi_color_maybe(Field::Id field_id, bool color)
|
||||||
{
|
{
|
||||||
const char* ansi;
|
const char* ansi;
|
||||||
|
|
||||||
@ -238,7 +215,7 @@ ansi_color_maybe(Field::Id field_id, gboolean color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ansi_reset_maybe(Field::Id field_id, gboolean color)
|
ansi_reset_maybe(Field::Id field_id, bool color)
|
||||||
{
|
{
|
||||||
if (!color)
|
if (!color)
|
||||||
return; /* nothing to do */
|
return; /* nothing to do */
|
||||||
@ -275,7 +252,7 @@ display_field(const Message& msg, Field::Id field_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
print_summary(const Message& msg, const MuConfig* opts)
|
print_summary(const Message& msg, const Options& opts)
|
||||||
{
|
{
|
||||||
const auto body{msg.body_text()};
|
const auto body{msg.body_text()};
|
||||||
if (!body)
|
if (!body)
|
||||||
@ -283,7 +260,7 @@ print_summary(const Message& msg, const MuConfig* opts)
|
|||||||
|
|
||||||
const auto summ{to_string_opt_gchar(
|
const auto summ{to_string_opt_gchar(
|
||||||
mu_str_summarize(body->c_str(),
|
mu_str_summarize(body->c_str(),
|
||||||
opts->summary_len))};
|
opts.find.summary_len.value_or(0)))};
|
||||||
|
|
||||||
g_print("Summary: ");
|
g_print("Summary: ");
|
||||||
mu_util_fputs_encoded(summ ? summ->c_str() : "<none>", stdout);
|
mu_util_fputs_encoded(summ ? summ->c_str() : "<none>", stdout);
|
||||||
@ -291,7 +268,7 @@ print_summary(const Message& msg, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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 is_root{any_of(info.flags & QueryMatch::Flags::Root)};
|
||||||
const auto first_child{any_of(info.flags & QueryMatch::Flags::First)};
|
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)};
|
// const auto is_related{any_of(info.flags & QueryMatch::Flags::Related)};
|
||||||
|
|
||||||
/* indent */
|
/* indent */
|
||||||
if (opts->debug) {
|
if (opts.debug) {
|
||||||
::fputs(info.thread_path.c_str(), stdout);
|
::fputs(info.thread_path.c_str(), stdout);
|
||||||
::fputs(" ", stdout);
|
::fputs(" ", stdout);
|
||||||
} else
|
} else
|
||||||
@ -322,18 +299,15 @@ thread_indent(const QueryMatch& info, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
output_plain_fields(const Message& msg, const char* fields,
|
output_plain_fields(const Message& msg, const std::string& fields,
|
||||||
gboolean color, gboolean threads)
|
bool color, bool threads)
|
||||||
{
|
{
|
||||||
const char* myfields;
|
size_t nonempty{};
|
||||||
int nonempty;
|
|
||||||
|
|
||||||
g_return_if_fail(fields);
|
for (auto&& k: fields) {
|
||||||
|
const auto field_opt{field_from_shortcut(k)};
|
||||||
for (myfields = fields, nonempty = 0; *myfields; ++myfields) {
|
|
||||||
const auto field_opt{field_from_shortcut(*myfields)};
|
|
||||||
if (!field_opt || (!field_opt->is_value() && !field_opt->is_contact()))
|
if (!field_opt || (!field_opt->is_value() && !field_opt->is_contact()))
|
||||||
nonempty += printf("%c", *myfields);
|
nonempty += printf("%c", k);
|
||||||
|
|
||||||
else {
|
else {
|
||||||
ansi_color_maybe(field_opt->id, color);
|
ansi_color_maybe(field_opt->id, color);
|
||||||
@ -347,29 +321,29 @@ output_plain_fields(const Message& msg, const char* fields,
|
|||||||
fputs("\n", stdout);
|
fputs("\n", stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static bool
|
||||||
output_plain(const Option<Message>& msg, const OutputInfo& info,
|
output_plain(const Option<Message>& msg, const OutputInfo& info,
|
||||||
const MuConfig* opts, GError** err)
|
const Options& opts, GError** err)
|
||||||
{
|
{
|
||||||
if (!msg)
|
if (!msg)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
/* we reuse the color (whatever that may be)
|
/* we reuse the color (whatever that may be)
|
||||||
* for message-priority for threads, too */
|
* for message-priority for threads, too */
|
||||||
ansi_color_maybe(Field::Id::Priority, !opts->nocolor);
|
ansi_color_maybe(Field::Id::Priority, !opts.nocolor);
|
||||||
if (opts->threads && info.match_info)
|
if (opts.find.threads && info.match_info)
|
||||||
thread_indent(*info.match_info, opts);
|
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);
|
print_summary(*msg, opts);
|
||||||
|
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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 (msg) {
|
||||||
if (const auto sexp{msg->sexp()}; !sexp.empty())
|
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
|
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) {
|
if (info.header) {
|
||||||
g_print("[\n");
|
g_print("[\n");
|
||||||
@ -416,7 +390,7 @@ print_attr_xml(const std::string& elm, const std::string& str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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) {
|
if (info.header) {
|
||||||
g_print("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
|
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
|
static OutputFunc
|
||||||
get_output_func(const MuConfig* opts, GError** err)
|
get_output_func(const Options& opts, GError** err)
|
||||||
{
|
{
|
||||||
switch (opts->format) {
|
if (!opts.find.exec.empty())
|
||||||
case MU_CONFIG_FORMAT_LINKS: return output_link;
|
return exec_cmd;
|
||||||
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;
|
|
||||||
|
|
||||||
|
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;
|
default: g_return_val_if_reached(NULL); return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
static Result<void>
|
||||||
output_query_results(const QueryResults& qres, const MuConfig* opts)
|
output_query_results(const QueryResults& qres, const Options& opts)
|
||||||
{
|
{
|
||||||
GError* err{};
|
GError* err{};
|
||||||
const auto output_func{get_output_func(opts, &err)};
|
const auto output_func{get_output_func(opts, &err)};
|
||||||
if (!output_func)
|
if (!output_func)
|
||||||
return Err(Error::Code::Query, &err, "failed to find output function");
|
return Err(Error::Code::Query, &err, "failed to find output function");
|
||||||
|
|
||||||
gboolean rv{true};
|
bool rv{true};
|
||||||
output_func(Nothing, FirstOutput, opts, {});
|
output_func(Nothing, FirstOutput, opts, {});
|
||||||
|
|
||||||
size_t n{0};
|
size_t n{0};
|
||||||
@ -477,7 +454,7 @@ output_query_results(const QueryResults& qres, const MuConfig* opts)
|
|||||||
if (!msg)
|
if (!msg)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (opts->after != 0 && msg->changed() < opts->after)
|
if (msg->changed() < opts.find.after.value_or(0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
rv = output_func(msg,
|
rv = output_func(msg,
|
||||||
@ -501,7 +478,7 @@ output_query_results(const QueryResults& qres, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
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)};
|
auto qres{run_query(store, expr, opts)};
|
||||||
if (!qres)
|
if (!qres)
|
||||||
@ -513,98 +490,17 @@ process_query(const Store& store, const std::string& expr, const MuConfig* opts)
|
|||||||
return output_query_results(*qres, opts);
|
return output_query_results(*qres, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
Result<void>
|
||||||
execute_find(const Store& store, const MuConfig* opts)
|
Mu::mu_cmd_find(const Store& store, const Options& opts)
|
||||||
{
|
{
|
||||||
auto expr{get_query(opts)};
|
auto expr{get_query(opts)};
|
||||||
if (!expr)
|
if (!expr)
|
||||||
return Err(expr.error());
|
return Err(expr.error());
|
||||||
|
|
||||||
if (opts->format == MU_CONFIG_FORMAT_XQUERY)
|
if (opts.find.format == Format::XQuery)
|
||||||
return print_internal(store, *expr, TRUE, FALSE);
|
return print_internal(store, *expr, true, false);
|
||||||
else if (opts->format == MU_CONFIG_FORMAT_MQUERY)
|
else if (opts.find.format == Format::MQuery)
|
||||||
return print_internal(store, *expr, FALSE, opts->verbose);
|
return print_internal(store, *expr, false, opts.verbose);
|
||||||
else
|
else
|
||||||
return process_query(store, *expr, opts);
|
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 "index/mu-indexer.hh"
|
||||||
#include "mu-store.hh"
|
#include "mu-store.hh"
|
||||||
#include "mu-runtime.hh"
|
|
||||||
|
|
||||||
#include "utils/mu-util.h"
|
#include "utils/mu-util.h"
|
||||||
|
|
||||||
@ -78,24 +77,17 @@ print_stats(const Indexer::Progress& stats, bool color)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result<void>
|
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};
|
const auto mdir{store.properties().root_maildir};
|
||||||
if (G_UNLIKELY(access(mdir.c_str(), R_OK) != 0))
|
if (G_UNLIKELY(access(mdir.c_str(), R_OK) != 0))
|
||||||
return Err(Error::Code::File, "'%s' is not readable: %s",
|
return Err(Error::Code::File, "'%s' is not readable: %s",
|
||||||
mdir.c_str(), g_strerror(errno));
|
mdir.c_str(), g_strerror(errno));
|
||||||
|
|
||||||
MaybeAnsi col{!opts->nocolor};
|
MaybeAnsi col{!opts.nocolor};
|
||||||
using Color = MaybeAnsi::Color;
|
using Color = MaybeAnsi::Color;
|
||||||
if (!opts->quiet) {
|
if (!opts.quiet) {
|
||||||
if (opts->lazycheck)
|
if (opts.index.lazycheck)
|
||||||
std::cout << "lazily ";
|
std::cout << "lazily ";
|
||||||
|
|
||||||
std::cout << "indexing maildir " << col.fg(Color::Green)
|
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{};
|
Mu::Indexer::Config conf{};
|
||||||
conf.cleanup = !opts->nocleanup;
|
conf.cleanup = !opts.index.nocleanup;
|
||||||
conf.lazy_check = opts->lazycheck;
|
conf.lazy_check = opts.index.lazycheck;
|
||||||
// ignore .noupdate with an empty store.
|
// ignore .noupdate with an empty store.
|
||||||
conf.ignore_noupdate = store.empty();
|
conf.ignore_noupdate = store.empty();
|
||||||
|
|
||||||
@ -115,12 +107,12 @@ Mu::mu_cmd_index(Mu::Store& store, const MuConfig* opts)
|
|||||||
auto& indexer{store.indexer()};
|
auto& indexer{store.indexer()};
|
||||||
indexer.start(conf);
|
indexer.start(conf);
|
||||||
while (!caught_signal && indexer.is_running()) {
|
while (!caught_signal && indexer.is_running()) {
|
||||||
if (!opts->quiet)
|
if (!opts.quiet)
|
||||||
print_stats(indexer.progress(), !opts->nocolor);
|
print_stats(indexer.progress(), !opts.nocolor);
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||||
|
|
||||||
if (!opts->quiet) {
|
if (!opts.quiet) {
|
||||||
std::cout << "\r";
|
std::cout << "\r";
|
||||||
std::cout.flush();
|
std::cout.flush();
|
||||||
}
|
}
|
||||||
@ -128,8 +120,8 @@ Mu::mu_cmd_index(Mu::Store& store, const MuConfig* opts)
|
|||||||
|
|
||||||
store.indexer().stop();
|
store.indexer().stop();
|
||||||
|
|
||||||
if (!opts->quiet) {
|
if (!opts.quiet) {
|
||||||
print_stats(store.indexer().progress(), !opts->nocolor);
|
print_stats(store.indexer().progress(), !opts.nocolor);
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,6 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "mu-runtime.hh"
|
|
||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-server.hh"
|
#include "mu-server.hh"
|
||||||
|
|
||||||
@ -111,9 +110,9 @@ report_error(const Mu::Error& err) noexcept
|
|||||||
|
|
||||||
|
|
||||||
Result<void>
|
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);
|
Store::Options::Writable);
|
||||||
if (!store)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
@ -123,20 +122,18 @@ Mu::mu_cmd_server(const MuConfig* opts) try {
|
|||||||
"readline: %s",
|
"readline: %s",
|
||||||
store->properties().database_path.c_str(),
|
store->properties().database_path.c_str(),
|
||||||
store->properties().root_maildir.c_str(),
|
store->properties().root_maildir.c_str(),
|
||||||
opts->debug ? "yes" : "no",
|
opts.debug ? "yes" : "no",
|
||||||
have_readline() ? "yes" : "no");
|
have_readline() ? "yes" : "no");
|
||||||
|
|
||||||
tty = ::isatty(::fileno(stdout));
|
tty = ::isatty(::fileno(stdout));
|
||||||
const auto eval = std::string{opts->commands ? "(help :full t)"
|
const auto eval = std::string{opts.server.commands ? "(help :full t)" : opts.server.eval};
|
||||||
: opts->eval ? opts->eval
|
|
||||||
: ""};
|
|
||||||
if (!eval.empty()) {
|
if (!eval.empty()) {
|
||||||
server.invoke(eval);
|
server.invoke(eval);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note, the readline stuff is inactive unless on a tty.
|
// 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);
|
setup_readline(histpath, 50);
|
||||||
|
|
||||||
install_sig_handler();
|
install_sig_handler();
|
||||||
|
|||||||
238
mu/mu-cmd.cc
238
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
|
** 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
|
** under the terms of the GNU General Public License as published by the
|
||||||
@ -28,11 +28,10 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#include "mu-config.hh"
|
#include "mu-options.hh"
|
||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-maildir.hh"
|
#include "mu-maildir.hh"
|
||||||
#include "mu-contacts-cache.hh"
|
#include "mu-contacts-cache.hh"
|
||||||
#include "mu-runtime.hh"
|
|
||||||
#include "message/mu-message.hh"
|
#include "message/mu-message.hh"
|
||||||
#include "message/mu-mime-object.hh"
|
#include "message/mu-mime-object.hh"
|
||||||
|
|
||||||
@ -49,7 +48,7 @@
|
|||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
|
|
||||||
static Mu::Result<void>
|
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(message.sexp().to_string().c_str(), stdout);
|
||||||
::fputs("\n", 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 */
|
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;
|
std::string str;
|
||||||
seq_for_each(message.parts(), [&](auto&& part) {
|
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 */
|
/* a summary_len of 0 mean 'don't show summary, show body */
|
||||||
static void
|
static void
|
||||||
body_or_summary(const Message& message, const MuConfig* opts)
|
body_or_summary(const Message& message, const Options& opts)
|
||||||
{
|
{
|
||||||
gboolean color;
|
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()};
|
const auto body{message.body_text()};
|
||||||
if (!body || body->empty()) {
|
if (!body || body->empty()) {
|
||||||
@ -121,9 +119,9 @@ body_or_summary(const Message& message, const MuConfig* opts)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->summary_len != 0) {
|
if (opts.view.summary_len) {
|
||||||
gchar* summ;
|
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);
|
print_field("Summary", summ, color);
|
||||||
g_free(summ);
|
g_free(summ);
|
||||||
} else {
|
} else {
|
||||||
@ -136,9 +134,9 @@ body_or_summary(const Message& message, const MuConfig* opts)
|
|||||||
/* we ignore fields for now */
|
/* we ignore fields for now */
|
||||||
/* summary_len == 0 means "no summary */
|
/* summary_len == 0 means "no summary */
|
||||||
static Mu::Result<void>
|
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("From", to_string(message.from()), color);
|
||||||
print_field("To", to_string(message.to()), 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>
|
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)
|
if (!message)
|
||||||
return Err(message.error());
|
return Err(message.error());
|
||||||
|
|
||||||
switch (opts->format) {
|
switch (opts.view.format) {
|
||||||
case MU_CONFIG_FORMAT_PLAIN:
|
case Format::Plain:
|
||||||
return view_msg_plain(*message, opts);
|
return view_msg_plain(*message, opts);
|
||||||
case MU_CONFIG_FORMAT_SEXP:
|
case Format::Sexp:
|
||||||
return view_msg_sexp(*message, opts);
|
return view_msg_sexp(*message, opts);
|
||||||
default:
|
default:
|
||||||
g_critical("bug: should not be reached");
|
g_critical("bug: should not be reached");
|
||||||
@ -176,35 +176,13 @@ handle_msg(const std::string& fname, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Mu::Result<void>
|
static Mu::Result<void>
|
||||||
view_params_valid(const MuConfig* opts)
|
cmd_view(const Options& opts)
|
||||||
{
|
{
|
||||||
/* note: params[0] will be 'view' */
|
for (auto&& file: opts.view.files) {
|
||||||
if (!opts->params[0] || !opts->params[1])
|
if (auto res = handle_msg(file, opts); !res)
|
||||||
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)
|
|
||||||
return res;
|
return res;
|
||||||
/* add a separator between two messages? */
|
/* add a separator between two messages? */
|
||||||
if (opts->terminator)
|
if (opts.view.terminate)
|
||||||
g_print("%c", VIEW_TERMINATOR);
|
g_print("%c", VIEW_TERMINATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,17 +190,11 @@ cmd_view(const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Mu::Result<void>
|
static Mu::Result<void>
|
||||||
cmd_mkdir(const MuConfig* opts)
|
cmd_mkdir(const Options& opts)
|
||||||
{
|
{
|
||||||
int i;
|
for (auto&& dir: opts.mkdir.dirs) {
|
||||||
|
|
||||||
if (!opts->params[1])
|
|
||||||
return Err(Error::Code::InvalidArgument,
|
|
||||||
"missing directory parameter");
|
|
||||||
|
|
||||||
for (i = 1; opts->params[i]; ++i) {
|
|
||||||
if (auto&& res =
|
if (auto&& res =
|
||||||
maildir_mkdir(opts->params[i], opts->dirmode, FALSE); !res)
|
maildir_mkdir(dir, opts.mkdir.mode); !res)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,42 +202,29 @@ cmd_mkdir(const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
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' */
|
for (auto&& file: opts.add.files) {
|
||||||
if (!opts->params[0] || !opts->params[1])
|
const auto docid{store.add_message(file)};
|
||||||
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])};
|
|
||||||
if (!docid)
|
if (!docid)
|
||||||
return Err(docid.error());
|
return Err(docid.error());
|
||||||
else
|
else
|
||||||
g_debug("added message @ %s, docid=%u",
|
g_debug("added message @ %s, docid=%u",
|
||||||
opts->params[u], docid.value());
|
file.c_str(), docid.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
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' */
|
for (auto&& file: opts.remove.files) {
|
||||||
if (!opts->params[0] || !opts->params[1])
|
const auto res = store.remove_message(file);
|
||||||
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]);
|
|
||||||
if (!res)
|
if (!res)
|
||||||
return Err(Error::Code::File, "failed to remove %s",
|
return Err(Error::Code::File, "failed to remove %s", file.c_str());
|
||||||
opts->params[u]);
|
|
||||||
else
|
else
|
||||||
g_debug("removed message @ %s", opts->params[u]);
|
g_debug("removed message @ %s", file.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
@ -286,9 +245,9 @@ key_val(const Mu::MaybeAnsi& col, const std::string& key, T val)
|
|||||||
|
|
||||||
|
|
||||||
static void
|
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()};
|
const auto created{sig.created()};
|
||||||
key_val(col, "created",
|
key_val(col, "created",
|
||||||
@ -318,10 +277,10 @@ print_signature(const Mu::MimeSignature& sig, const MuConfig *opts)
|
|||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
verify(const MimeMultipartSigned& sigpart, const MuConfig *opts)
|
verify(const MimeMultipartSigned& sigpart, const Options& opts)
|
||||||
{
|
{
|
||||||
using VFlags = MimeMultipartSigned::VerifyFlags;
|
using VFlags = MimeMultipartSigned::VerifyFlags;
|
||||||
const auto vflags{opts->auto_retrieve ?
|
const auto vflags{opts.verify.auto_retrieve ?
|
||||||
VFlags::EnableKeyserverLookups: VFlags::None};
|
VFlags::EnableKeyserverLookups: VFlags::None};
|
||||||
|
|
||||||
auto ctx{MimeCryptoContext::make_gpg()};
|
auto ctx{MimeCryptoContext::make_gpg()};
|
||||||
@ -329,11 +288,11 @@ verify(const MimeMultipartSigned& sigpart, const MuConfig *opts)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto sigs{sigpart.verify(*ctx, vflags)};
|
const auto sigs{sigpart.verify(*ctx, vflags)};
|
||||||
Mu::MaybeAnsi col{!opts->nocolor};
|
Mu::MaybeAnsi col{!opts.nocolor};
|
||||||
|
|
||||||
if (!sigs || sigs->empty()) {
|
if (!sigs || sigs->empty()) {
|
||||||
|
|
||||||
if (!opts->quiet)
|
if (!opts.quiet)
|
||||||
g_print("cannot find signatures in part\n");
|
g_print("cannot find signatures in part\n");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -344,10 +303,10 @@ verify(const MimeMultipartSigned& sigpart, const MuConfig *opts)
|
|||||||
|
|
||||||
const auto status{sig.status()};
|
const auto status{sig.status()};
|
||||||
|
|
||||||
if (!opts->quiet)
|
if (!opts.quiet)
|
||||||
key_val(col, "status", to_string(status));
|
key_val(col, "status", to_string(status));
|
||||||
|
|
||||||
if (opts->verbose)
|
if (opts.verbose)
|
||||||
print_signature(sig, opts);
|
print_signature(sig, opts);
|
||||||
|
|
||||||
if (none_of(sig.status() & MimeSignature::Status::Green))
|
if (none_of(sig.status() & MimeSignature::Status::Green))
|
||||||
@ -358,25 +317,24 @@ verify(const MimeMultipartSigned& sigpart, const MuConfig *opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Mu::Result<void>
|
static Mu::Result<void>
|
||||||
cmd_verify(const MuConfig* opts)
|
cmd_verify(const Options& opts)
|
||||||
{
|
{
|
||||||
if (!opts || opts->cmd != MU_CONFIG_CMD_VERIFY)
|
bool all_ok{true};
|
||||||
return Err(Error::Code::Internal, "error in parameters");
|
const auto mopts = message_options(opts.verify);
|
||||||
|
|
||||||
if (!opts->params[1])
|
for (auto&& file: opts.verify.files) {
|
||||||
return Err(Error::Code::InvalidArgument,
|
|
||||||
"missing message-file parameter");
|
|
||||||
|
|
||||||
auto message{Message::make_from_path(opts->params[1],
|
auto message{Message::make_from_path(file, mopts)};
|
||||||
mu_config_message_options(opts))};
|
|
||||||
if (!message)
|
if (!message)
|
||||||
return Err(message.error());
|
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 (none_of(message->flags() & Flags::Signed)) {
|
||||||
if (!opts->quiet)
|
if (!opts.quiet)
|
||||||
g_print("no signed parts found\n");
|
g_print("%s: no signed parts found\n", file.c_str());
|
||||||
return Ok();
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verified{true}; /* innocent until proven guilty */
|
bool verified{true}; /* innocent until proven guilty */
|
||||||
@ -393,7 +351,10 @@ cmd_verify(const MuConfig* opts)
|
|||||||
verified = false;
|
verified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verified)
|
all_ok = all_ok && verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all_ok)
|
||||||
return Ok();
|
return Ok();
|
||||||
else
|
else
|
||||||
return Err(Error::Code::UnverifiedSignature,
|
return Err(Error::Code::UnverifiedSignature,
|
||||||
@ -401,7 +362,7 @@ cmd_verify(const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
static Result<void>
|
||||||
cmd_info(const Mu::Store& store, const MuConfig* opts)
|
cmd_info(const Mu::Store& store, const Options& opts)
|
||||||
{
|
{
|
||||||
using namespace tabulate;
|
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-change", tstamp(store.statistics().last_change)});
|
||||||
info.add_row({"last-index", tstamp(store.statistics().last_index)});
|
info.add_row({"last-index", tstamp(store.statistics().last_index)});
|
||||||
|
|
||||||
if (!opts->nocolor)
|
if (!opts.nocolor)
|
||||||
colorify(info);
|
colorify(info);
|
||||||
|
|
||||||
std::cout << info << '\n';
|
std::cout << info << '\n';
|
||||||
@ -451,38 +412,24 @@ cmd_info(const Mu::Store& store, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
static Result<void>
|
||||||
cmd_init(const MuConfig* opts)
|
cmd_init(const Options& opts)
|
||||||
{
|
{
|
||||||
/* not provided, nor could we find a good default */
|
/* not provided, nor could we find a good default */
|
||||||
if (!opts->maildir)
|
if (opts.init.maildir.empty())
|
||||||
return Err(Error::Code::InvalidArgument,
|
return Err(Error::Code::InvalidArgument,
|
||||||
"missing --maildir parameter and could "
|
"missing --maildir parameter and could "
|
||||||
"not determine default");
|
"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{};
|
Mu::Store::Config conf{};
|
||||||
conf.max_message_size = opts->max_msg_size;
|
conf.max_message_size = opts.init.max_msg_size.value_or(0);
|
||||||
conf.batch_size = opts->batch_size;
|
conf.batch_size = opts.init.batch_size.value_or(0);
|
||||||
|
|
||||||
Mu::StringVec my_addrs;
|
auto store = Store::make_new(opts.runtime_path(RuntimePath::XapianDb),
|
||||||
auto addrs = opts->my_addresses;
|
opts.init.maildir, opts.init.my_addresses, conf);
|
||||||
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);
|
|
||||||
if (!store)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
|
|
||||||
if (!opts->quiet) {
|
if (!opts.quiet) {
|
||||||
cmd_info(*store, opts);
|
cmd_info(*store, opts);
|
||||||
std::cout << "\nstore created; use the 'index' command to fill/update it.\n";
|
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>
|
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)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
else
|
else
|
||||||
@ -511,13 +458,13 @@ show_usage(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
using ReadOnlyStoreFunc = std::function<Result<void>(const Store&, const MuConfig*)>;
|
using ReadOnlyStoreFunc = std::function<Result<void>(const Store&, const Options&)>;
|
||||||
using WritableStoreFunc = std::function<Result<void>(Store&, const MuConfig*)>;
|
using WritableStoreFunc = std::function<Result<void>(Store&, const Options&)>;
|
||||||
|
|
||||||
static Result<void>
|
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)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
|
|
||||||
@ -525,9 +472,9 @@ with_readonly_store(const ReadOnlyStoreFunc& func, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Result<void>
|
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)};
|
Store::Options::Writable)};
|
||||||
if (!store)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
@ -536,54 +483,53 @@ with_writable_store(const WritableStoreFunc func, const MuConfig* opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result<void>
|
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])
|
if (!opts.sub_command)
|
||||||
return Err(Error::Code::InvalidArgument, "error in parameters");
|
return Err(Error::Code::Internal, "missing subcommand");
|
||||||
|
|
||||||
switch (opts->cmd) {
|
|
||||||
case MU_CONFIG_CMD_HELP: /* already handled in mu-config.c */
|
|
||||||
return Ok();
|
|
||||||
|
|
||||||
|
switch (*opts.sub_command) {
|
||||||
|
case Options::SubCommand::Help:
|
||||||
|
return Ok(); /* already handled in mu-options.cc */
|
||||||
/*
|
/*
|
||||||
* no store needed
|
* no store needed
|
||||||
*/
|
*/
|
||||||
case MU_CONFIG_CMD_FIELDS:
|
case Options::SubCommand::Fields:
|
||||||
return mu_cmd_fields(opts);
|
return mu_cmd_fields(opts);
|
||||||
case MU_CONFIG_CMD_MKDIR:
|
case Options::SubCommand::Mkdir:
|
||||||
return cmd_mkdir(opts);
|
return cmd_mkdir(opts);
|
||||||
case MU_CONFIG_CMD_SCRIPT:
|
case Options::SubCommand::Script:
|
||||||
return mu_cmd_script(opts);
|
return mu_cmd_script(opts);
|
||||||
case MU_CONFIG_CMD_VIEW:
|
case Options::SubCommand::View:
|
||||||
return cmd_view(opts);
|
return cmd_view(opts);
|
||||||
case MU_CONFIG_CMD_VERIFY:
|
case Options::SubCommand::Verify:
|
||||||
return cmd_verify(opts);
|
return cmd_verify(opts);
|
||||||
case MU_CONFIG_CMD_EXTRACT:
|
case Options::SubCommand::Extract:
|
||||||
return mu_cmd_extract(opts);
|
return mu_cmd_extract(opts);
|
||||||
/*
|
/*
|
||||||
* read-only store
|
* read-only store
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case MU_CONFIG_CMD_CFIND:
|
case Options::SubCommand::Cfind:
|
||||||
return with_readonly_store(mu_cmd_cfind, opts);
|
return with_readonly_store(mu_cmd_cfind, opts);
|
||||||
case MU_CONFIG_CMD_FIND:
|
case Options::SubCommand::Find:
|
||||||
return cmd_find(opts);
|
return cmd_find(opts);
|
||||||
case MU_CONFIG_CMD_INFO:
|
case Options::SubCommand::Info:
|
||||||
return with_readonly_store(cmd_info, opts);
|
return with_readonly_store(cmd_info, opts);
|
||||||
|
|
||||||
/* writable store */
|
/* writable store */
|
||||||
|
|
||||||
case MU_CONFIG_CMD_ADD:
|
case Options::SubCommand::Add:
|
||||||
return with_writable_store(cmd_add, opts);
|
return with_writable_store(cmd_add, opts);
|
||||||
case MU_CONFIG_CMD_REMOVE:
|
case Options::SubCommand::Remove:
|
||||||
return with_writable_store(cmd_remove, opts);
|
return with_writable_store(cmd_remove, opts);
|
||||||
case MU_CONFIG_CMD_INDEX:
|
case Options::SubCommand::Index:
|
||||||
return with_writable_store(mu_cmd_index, opts);
|
return with_writable_store(mu_cmd_index, opts);
|
||||||
|
|
||||||
/* commands instantiate store themselves */
|
/* commands instantiate store themselves */
|
||||||
case MU_CONFIG_CMD_INIT:
|
case Options::SubCommand::Init:
|
||||||
return cmd_init(opts);
|
return cmd_init(opts);
|
||||||
case MU_CONFIG_CMD_SERVER:
|
case Options::SubCommand::Server:
|
||||||
return mu_cmd_server(opts);
|
return mu_cmd_server(opts);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
46
mu/mu-cmd.hh
46
mu/mu-cmd.hh
@ -21,11 +21,37 @@
|
|||||||
#define MU_CMD_HH__
|
#define MU_CMD_HH__
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <mu-config.hh>
|
|
||||||
#include <mu-store.hh>
|
#include <mu-store.hh>
|
||||||
#include <utils/mu-result.hh>
|
#include <utils/mu-result.hh>
|
||||||
|
|
||||||
|
#include "mu-options.hh"
|
||||||
|
|
||||||
namespace Mu {
|
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
|
* execute the 'find' command
|
||||||
*
|
*
|
||||||
@ -34,7 +60,7 @@ namespace Mu {
|
|||||||
*
|
*
|
||||||
* @return Ok() or some error
|
* @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
|
* 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
|
* @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
|
* execute the 'fields' command
|
||||||
@ -52,7 +78,7 @@ Result<void> mu_cmd_extract(const MuConfig* opts);
|
|||||||
*
|
*
|
||||||
* @return Ok() or some error
|
* @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
|
* execute the 'script' command
|
||||||
@ -62,7 +88,7 @@ Result<void> mu_cmd_fields(const MuConfig* opts);
|
|||||||
*
|
*
|
||||||
* @return Ok() or some error
|
* @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
|
* execute the cfind command
|
||||||
@ -72,7 +98,7 @@ Result<void> mu_cmd_script(const MuConfig* opts);
|
|||||||
*
|
*
|
||||||
* @return Ok() or some error
|
* @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
|
* 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
|
* @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
|
* execute the server command
|
||||||
* @param opts configuration options
|
* @param opts configuration options
|
||||||
* @param err receives error information, or NULL
|
* @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'
|
* execute some mu command, based on 'opts'
|
||||||
@ -101,7 +127,7 @@ Result<void> mu_cmd_server(const MuConfig* opts);
|
|||||||
*
|
*
|
||||||
* @return Ok() or some error
|
* @return Ok() or some error
|
||||||
*/
|
*/
|
||||||
Result<void> mu_cmd_execute(const MuConfig* opts);
|
Result<void> mu_cmd_execute(const Options& opts);
|
||||||
|
|
||||||
} // namespace Mu
|
} // 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
|
|
||||||
780
mu/mu-options.cc
Normal file
780
mu/mu-options.cc
Normal file
@ -0,0 +1,780 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
|
**
|
||||||
|
** This program is free software; you can redistribute it and/or modify it
|
||||||
|
** under the terms of the GNU General Public License as published by the
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Command-line handling
|
||||||
|
*
|
||||||
|
* Here we implement mu's command-line parsing based on the CLI11 library. At
|
||||||
|
* the time of writing, that library seems to be the best based on the criteria
|
||||||
|
* that it supports the features we need and is available as a header-only
|
||||||
|
* include.
|
||||||
|
*
|
||||||
|
* CLI11 can do quite a bit, and we're only scratching the surface here,
|
||||||
|
* plan is to slowly improve things.
|
||||||
|
*
|
||||||
|
* - we do quite a bit of sanity-checking, but the errors are a rather terse
|
||||||
|
* - the docs could be improved, e.g., `mu find --help` and --format/--sortfield
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <utils/mu-utils.hh>
|
||||||
|
#include <utils/mu-error.hh>
|
||||||
|
#include "utils/mu-test-utils.hh"
|
||||||
|
#include "mu-options.hh"
|
||||||
|
#include "mu-script.hh"
|
||||||
|
|
||||||
|
#include <thirdparty/CLI11.hpp>
|
||||||
|
|
||||||
|
using namespace Mu;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options-specific array-bases type that maps some enum to a <name, description> pair
|
||||||
|
*/
|
||||||
|
template<typename T, std::size_t N>
|
||||||
|
using InfoEnum = AssocPairs<T, std::pair<std::string_view, std::string_view>, N>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name (shortname) for some InfoEnum, based on the enum
|
||||||
|
*
|
||||||
|
* @param ie an InfoEnum
|
||||||
|
* @param e an enum value
|
||||||
|
*
|
||||||
|
* @return the name if found, or Nothing
|
||||||
|
*/
|
||||||
|
template<typename IE>
|
||||||
|
static constexpr Option<std::string_view>
|
||||||
|
to_name(const IE& ie, typename IE::value_type::first_type e) {
|
||||||
|
if (auto&& s{to_second(ie, e)}; s)
|
||||||
|
return s->first;
|
||||||
|
else
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the enum value for some InfoEnum, based on the name
|
||||||
|
*
|
||||||
|
* @param ie an InfoEnum
|
||||||
|
* @param name some name (shortname)
|
||||||
|
*
|
||||||
|
* @return the name if found, or Nothing
|
||||||
|
*/
|
||||||
|
template<typename IE>
|
||||||
|
static constexpr Option<typename IE::value_type::first_type>
|
||||||
|
to_enum(const IE& ie, std::string_view name) {
|
||||||
|
for(auto&& item: ie)
|
||||||
|
if (item.second.first == name)
|
||||||
|
return item.first;
|
||||||
|
else
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List help options for as a string, with the default marked with '(*)'
|
||||||
|
*
|
||||||
|
* @param ie infoenum
|
||||||
|
* @param default_opt default option
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
template<typename IE>
|
||||||
|
static std::string
|
||||||
|
options_help(const IE& ie, typename IE::value_type::first_type default_opt)
|
||||||
|
{
|
||||||
|
std::string s;
|
||||||
|
for(auto&& item: ie) {
|
||||||
|
if (!s.empty())
|
||||||
|
s += ", ";
|
||||||
|
s += std::string{item.second.first};
|
||||||
|
if (item.first == default_opt)
|
||||||
|
s += "(*)"; /* default option */
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get map from string->type
|
||||||
|
*/
|
||||||
|
template<typename IE>
|
||||||
|
static std::unordered_map<std::string, typename IE::value_type::first_type>
|
||||||
|
options_map(const IE& ie)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, typename IE::value_type::first_type> map;
|
||||||
|
for (auto&& item : ie)
|
||||||
|
map.emplace(std::string{item.second.first}, item.first);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* common
|
||||||
|
*/
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void
|
||||||
|
sub_crypto(CLI::App& sub, T& opts)
|
||||||
|
{
|
||||||
|
sub.add_flag("--auto-retrieve,-r", opts.auto_retrieve,
|
||||||
|
"Attempt to automatically retrieve online keys");
|
||||||
|
sub.add_flag("--decrypt", opts.decrypt,
|
||||||
|
"Attempt to decrypt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* subcommands
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_add(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_option("files", opts.add.files,
|
||||||
|
"Path(s) to message files(s)")
|
||||||
|
->required();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_cfind(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
using Format = Options::Cfind::Format;
|
||||||
|
static constexpr InfoEnum<Format, 8> FormatInfos = {{
|
||||||
|
{ Format::Plain,
|
||||||
|
{"plain", "Plain output"}
|
||||||
|
},
|
||||||
|
{ Format::MuttAlias,
|
||||||
|
{"mutt-alias", "Mutt alias"}
|
||||||
|
},
|
||||||
|
{ Format::MuttAddressBook,
|
||||||
|
{"mutt-ab", "Mutt address book"}
|
||||||
|
},
|
||||||
|
{ Format::Wanderlust,
|
||||||
|
{"wl", "Wanderlust"}
|
||||||
|
},
|
||||||
|
{ Format::OrgContact,
|
||||||
|
{"org-contact", "org-contact"}
|
||||||
|
},
|
||||||
|
{ Format::Bbdb,
|
||||||
|
{"bbdb", "BBDB"}
|
||||||
|
},
|
||||||
|
{ Format::Csv,
|
||||||
|
{"csv", "comma-separated values"}
|
||||||
|
},
|
||||||
|
{ Format::Debug,
|
||||||
|
{"debug", "debug output"}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
const auto fhelp = options_help(FormatInfos, Format::Plain);
|
||||||
|
const auto fmap = options_map(FormatInfos);
|
||||||
|
|
||||||
|
sub.add_option("--format,-o", opts.cfind.format,
|
||||||
|
"Output format; one of " + fhelp)
|
||||||
|
->type_name("<format>")
|
||||||
|
->default_str("plain")
|
||||||
|
->default_val(Format::Plain)
|
||||||
|
->transform(CLI::CheckedTransformer(fmap));
|
||||||
|
|
||||||
|
sub.add_option("pattern", opts.cfind.rx_pattern,
|
||||||
|
"Regular expression pattern to match");
|
||||||
|
|
||||||
|
sub.add_flag("--personal,-p", opts.cfind.personal,
|
||||||
|
"Only show 'personal' contacts");
|
||||||
|
|
||||||
|
sub.add_option("--maxnum,-n", opts.cfind.maxnum,
|
||||||
|
"Maximum number of results")
|
||||||
|
->type_name("<number>")
|
||||||
|
->check(CLI::PositiveNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_extract(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub_crypto(sub, opts.extract);
|
||||||
|
|
||||||
|
sub.add_flag("--save-attachments,-a", opts.extract.save_attachments,
|
||||||
|
"Save all attachments");
|
||||||
|
sub.add_flag("--save-all", opts.extract.save_all, "Save all MIME parts")
|
||||||
|
->excludes("--save-attachments");
|
||||||
|
sub.add_flag("--overwrite", opts.extract.overwrite,
|
||||||
|
"Overwrite existing files");
|
||||||
|
sub.add_flag("--play", opts.extract.play,
|
||||||
|
"Attempt to open the extracted parts");
|
||||||
|
sub.add_option("--parts", opts.extract.parts,
|
||||||
|
"Save specific parts (comma-sep'd list)")
|
||||||
|
->type_name("<parts>")->delimiter(',');
|
||||||
|
sub.add_option("--target-dir", opts.extract.targetdir,
|
||||||
|
"Target directory for saving")
|
||||||
|
->type_name("<dir>")
|
||||||
|
->default_str("<current>")->default_val(".");
|
||||||
|
sub.add_option("message", opts.extract.message,
|
||||||
|
"Path to message file")->required()
|
||||||
|
->type_name("<message-path>");
|
||||||
|
sub.add_option("filename-rx", opts.extract.filename_rx,
|
||||||
|
"Regular expression for files to save")
|
||||||
|
->type_name("<filename-rx>")
|
||||||
|
->excludes("--parts")
|
||||||
|
->excludes("--save-attachments")
|
||||||
|
->excludes("--save-all");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_fields(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
// nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_find(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
using Format = Options::Find::Format;
|
||||||
|
static constexpr InfoEnum<Format, 7> FormatInfos = {{
|
||||||
|
{ Format::Plain,
|
||||||
|
{"plain", "Plain output"}
|
||||||
|
},
|
||||||
|
{ Format::Links,
|
||||||
|
{"links", "Maildir with symbolic links"}
|
||||||
|
},
|
||||||
|
{ Format::Xml,
|
||||||
|
{"xml", "XML"}
|
||||||
|
},
|
||||||
|
{ Format::Sexp,
|
||||||
|
{"sexp", "S-expressions"}
|
||||||
|
},
|
||||||
|
{ Format::XQuery,
|
||||||
|
{"xquery", "Show Xapian query (for debugging)"}
|
||||||
|
},
|
||||||
|
{ Format::MQuery,
|
||||||
|
{"mquery", "Show mu query for (for debugging)"}
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
|
||||||
|
sub.add_flag("--threads,-t", opts.find.threads,
|
||||||
|
"Show message threads");
|
||||||
|
sub.add_flag("--skip-dups,-u", opts.find.skip_dups,
|
||||||
|
"Show only one of messages with same message-id");
|
||||||
|
sub.add_flag("--include-related,-r", opts.find.include_related,
|
||||||
|
"Include related messages in results");
|
||||||
|
|
||||||
|
const auto fhelp = options_help(FormatInfos, Format::Plain);
|
||||||
|
const auto fmap = options_map(FormatInfos);
|
||||||
|
|
||||||
|
sub.add_option("--format,-o", opts.find.format,
|
||||||
|
"Output format; one of " + fhelp)
|
||||||
|
->type_name("<format>")
|
||||||
|
->default_str("plain")
|
||||||
|
->default_val(Format::Plain)
|
||||||
|
->transform(CLI::CheckedTransformer(fmap));
|
||||||
|
|
||||||
|
sub.add_option("--maxnum,-n", opts.find.maxnum,
|
||||||
|
"Maximum number of results")
|
||||||
|
->type_name("<number>")
|
||||||
|
->check(CLI::PositiveNumber);
|
||||||
|
|
||||||
|
sub.add_option("--fields,-f", opts.find.fields,
|
||||||
|
"Fields to display")
|
||||||
|
->default_val("d f s");
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Field::Id> smap;
|
||||||
|
std::string sopts;
|
||||||
|
field_for_each([&](auto&& field){
|
||||||
|
if (field.is_searchable()) {
|
||||||
|
smap.emplace(std::string(field.name), field.id);
|
||||||
|
if (!sopts.empty())
|
||||||
|
sopts += ", ";
|
||||||
|
sopts += format("%.*s|%c",STR_V(field.name), field.shortcut);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sub.add_option("--sortfield,-s", opts.find.sortfield,
|
||||||
|
"Field to sort the results by; one of " + sopts)
|
||||||
|
->type_name("<field>")
|
||||||
|
->default_str("date")
|
||||||
|
->default_val(Field::Id::Date)
|
||||||
|
->transform(CLI::CheckedTransformer(smap));
|
||||||
|
|
||||||
|
sub.add_option("--bookmark,-b", opts.find.bookmark,
|
||||||
|
"Use bookmarked query")
|
||||||
|
->type_name("<bookmark>");
|
||||||
|
|
||||||
|
sub.add_flag("--clearlinks", opts.find.clearlinks,
|
||||||
|
"Clear old links first");
|
||||||
|
sub.add_option("--linksdir", opts.find.linksdir,
|
||||||
|
"Use bookmarked query")
|
||||||
|
->type_name("<dir>");
|
||||||
|
|
||||||
|
sub.add_option("--summary-len", opts.find.summary_len,
|
||||||
|
"Use up to so many lines for the summary")
|
||||||
|
->type_name("<lines>")
|
||||||
|
->check(CLI::PositiveNumber);
|
||||||
|
|
||||||
|
sub.add_option("--exec", opts.find.exec,
|
||||||
|
"Command to execute on message file")
|
||||||
|
->type_name("<command>");
|
||||||
|
|
||||||
|
sub.add_option("query", opts.find.query, "Search query pattern(s)")
|
||||||
|
->type_name("<query>");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_help(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_option("command", opts.help.command,
|
||||||
|
"Command to request help for")
|
||||||
|
->type_name("<command>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_index(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_flag("--lazy-check", opts.index.lazycheck,
|
||||||
|
"Skip based on dir-timestamps");
|
||||||
|
sub.add_flag("--nocleanup", opts.index.nocleanup,
|
||||||
|
"Don't clean up database after indexing");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_info(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
// nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_init(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_option("--maildir,-m", opts.init.maildir,
|
||||||
|
"Top of the maildir")
|
||||||
|
->type_name("<maildir>");
|
||||||
|
sub.add_option("--my-address", opts.init.my_addresses,
|
||||||
|
"Personal e-mail addresses")
|
||||||
|
->type_name("<addresses>");
|
||||||
|
sub.add_option("--max-message-size", opts.init.max_msg_size,
|
||||||
|
"Maximum allowed message size in bytes");
|
||||||
|
sub.add_option("--batch-size", opts.init.max_msg_size,
|
||||||
|
"Maximum size of database transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_mkdir(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_option("--mode", opts.mkdir.mode, "Set the access mode (octal)")
|
||||||
|
->default_val(0755)
|
||||||
|
->type_name("<mode>");
|
||||||
|
|
||||||
|
sub.add_option("dirs", opts.mkdir.dirs, "Path to directory/ies")
|
||||||
|
->type_name("<dir>")
|
||||||
|
->required();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_remove(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_option("files", opts.remove.files,
|
||||||
|
"Paths to message files to remove")
|
||||||
|
->type_name("<files>");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_server(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_flag("--commands", opts.server.commands,
|
||||||
|
"List available commands");
|
||||||
|
sub.add_option("--eval", opts.server.eval,
|
||||||
|
"Evaluate mu server expression")
|
||||||
|
->excludes("--commands");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_verify(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub_crypto(sub, opts.verify);
|
||||||
|
|
||||||
|
sub.add_option("files", opts.verify.files,
|
||||||
|
"Message files to verify")
|
||||||
|
->type_name("<message-file>")
|
||||||
|
->required();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sub_view(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
using Format = Options::View::Format;
|
||||||
|
static constexpr InfoEnum<Format, 2> FormatInfos = {{
|
||||||
|
{ Format::Plain,
|
||||||
|
{"plain", "Plain output"}
|
||||||
|
},
|
||||||
|
{ Format::Sexp,
|
||||||
|
{"sexp", "S-expressions"}
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
|
||||||
|
const auto fhelp = options_help(FormatInfos, Format::Plain);
|
||||||
|
const auto fmap = options_map(FormatInfos);
|
||||||
|
|
||||||
|
sub.add_option("--format,-o", opts.view.format,
|
||||||
|
"Output format; one of " + fhelp)
|
||||||
|
->type_name("<format>")
|
||||||
|
->default_str("plain")
|
||||||
|
->default_val(Format::Plain)
|
||||||
|
->transform(CLI::CheckedTransformer(fmap));
|
||||||
|
|
||||||
|
sub_crypto(sub, opts.view);
|
||||||
|
|
||||||
|
sub.add_option("--summary-len", opts.view.summary_len,
|
||||||
|
"Use up to so many lines for the summary")
|
||||||
|
->type_name("<lines>")
|
||||||
|
->check(CLI::PositiveNumber);
|
||||||
|
|
||||||
|
sub.add_flag("--terminate", opts.view.terminate,
|
||||||
|
"Insert form-feed after each message");
|
||||||
|
|
||||||
|
sub.add_option("files", opts.view.files,
|
||||||
|
"Message files to view")
|
||||||
|
->type_name("<file>")
|
||||||
|
->required();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using SubCommand = Options::SubCommand;
|
||||||
|
using Category = Options::Category;
|
||||||
|
|
||||||
|
struct CommandInfo {
|
||||||
|
Category category;
|
||||||
|
std::string_view name;
|
||||||
|
std::string_view help;
|
||||||
|
|
||||||
|
// std::function is not constexp-friendly
|
||||||
|
typedef void(*setup_func_t)(CLI::App&, Options&);
|
||||||
|
setup_func_t setup_func{};
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr
|
||||||
|
AssocPairs<SubCommand, CommandInfo, Options::SubCommandNum> SubCommandInfos= {{
|
||||||
|
{ SubCommand::Add,
|
||||||
|
{ Category::NeedsWritableStore,
|
||||||
|
"add", "Add message(s) to the database", sub_add}
|
||||||
|
},
|
||||||
|
{ SubCommand::Cfind,
|
||||||
|
{ Category::NeedsReadOnlyStore,
|
||||||
|
"cfind", "Find contacts matching pattern", sub_cfind}
|
||||||
|
},
|
||||||
|
{ SubCommand::Extract,
|
||||||
|
{Category::None,
|
||||||
|
"extract", "Extract MIME-parts from messages", sub_extract}
|
||||||
|
},
|
||||||
|
{ SubCommand::Fields,
|
||||||
|
{Category::None,
|
||||||
|
"fields", "Show a information about search fields", sub_fields}
|
||||||
|
},
|
||||||
|
{ SubCommand::Find,
|
||||||
|
{Category::NeedsReadOnlyStore,
|
||||||
|
"find", "Find messages matching query", sub_find }
|
||||||
|
},
|
||||||
|
{ SubCommand::Help,
|
||||||
|
{Category::None,
|
||||||
|
"help", "Show help information", sub_help }
|
||||||
|
},
|
||||||
|
{ SubCommand::Index,
|
||||||
|
{Category::NeedsWritableStore,
|
||||||
|
"index", "Store message information in the database", sub_index }
|
||||||
|
},
|
||||||
|
{ SubCommand::Info,
|
||||||
|
{Category::NeedsReadOnlyStore,
|
||||||
|
"info", "Show information about the message store database", sub_info }
|
||||||
|
},
|
||||||
|
{ SubCommand::Init,
|
||||||
|
{Category::NeedsWritableStore,
|
||||||
|
"init", "Initialize the database", sub_init }
|
||||||
|
},
|
||||||
|
{ SubCommand::Mkdir,
|
||||||
|
{Category::None,
|
||||||
|
"mkdir", "Create a new Maildir", sub_mkdir }
|
||||||
|
},
|
||||||
|
{ SubCommand::Remove,
|
||||||
|
{Category::NeedsWritableStore,
|
||||||
|
"remove", "Remove message from file-system and database", sub_remove }
|
||||||
|
},
|
||||||
|
{ SubCommand::Script,
|
||||||
|
// Note: SubCommand::Script is special; there's no literal
|
||||||
|
// "script" subcommand, there subcommands for all the scripts.
|
||||||
|
{Category::None,
|
||||||
|
"script", "Invoke a script", {}}
|
||||||
|
},
|
||||||
|
{ SubCommand::Server,
|
||||||
|
{Category::NeedsWritableStore,
|
||||||
|
"server", "Start a mu server (for mu4e)", sub_server}
|
||||||
|
},
|
||||||
|
{ SubCommand::Verify,
|
||||||
|
{Category::None,
|
||||||
|
"verify", "Verify cryptographic signatures", sub_verify}
|
||||||
|
},
|
||||||
|
{ SubCommand::View,
|
||||||
|
{Category::None,
|
||||||
|
"view", "View specific messages", sub_view}
|
||||||
|
},
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static ScriptInfos
|
||||||
|
add_scripts(CLI::App& app, Options& opts)
|
||||||
|
{
|
||||||
|
#ifndef BUILD_GUILE
|
||||||
|
return {};
|
||||||
|
#else
|
||||||
|
ScriptPaths paths = { MU_SCRIPTS_DIR };
|
||||||
|
auto scriptinfos{script_infos(paths)};
|
||||||
|
for (auto&& script: scriptinfos) {
|
||||||
|
auto&& sub = app.add_subcommand(script.name)->group("Scripts")
|
||||||
|
->description(script.oneline);
|
||||||
|
sub->add_option("params", opts.script.params,
|
||||||
|
"Parameter to script")
|
||||||
|
->type_name("<params>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptinfos;
|
||||||
|
#endif /*BUILD_GUILE*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Result<Options>
|
||||||
|
show_manpage(Options& opts, const std::string& name)
|
||||||
|
{
|
||||||
|
char *path = g_find_program_in_path("man");
|
||||||
|
if (!path)
|
||||||
|
return Err(Error::Code::Command,
|
||||||
|
"cannot find 'man' program");
|
||||||
|
|
||||||
|
GError* err{};
|
||||||
|
auto cmd{to_string_gchar(std::move(path)) + " " + name};
|
||||||
|
auto res = g_spawn_command_line_sync(cmd.c_str(), {}, {}, {}, &err);
|
||||||
|
if (!res)
|
||||||
|
return Err(Error::Code::Command, &err,
|
||||||
|
"error running man command");
|
||||||
|
|
||||||
|
return Ok(std::move(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Result<Options>
|
||||||
|
cmd_help(const CLI::App& app, Options& opts)
|
||||||
|
{
|
||||||
|
if (opts.help.command.empty()) {
|
||||||
|
std::cout << app.help() << "\n";
|
||||||
|
return Ok(std::move(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&& item: SubCommandInfos) {
|
||||||
|
if (item.second.name == opts.help.command)
|
||||||
|
return show_manpage(opts, "mu-" + opts.help.command);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&& item: {"query", "easy"})
|
||||||
|
if (item == opts.help.command)
|
||||||
|
return show_manpage(opts, "mu-" + opts.help.command);
|
||||||
|
|
||||||
|
return Err(Error::Code::Command,
|
||||||
|
"no help available for '%s'", opts.help.command.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_global_options(CLI::App& cli, Options& opts)
|
||||||
|
{
|
||||||
|
static const auto default_no_color =
|
||||||
|
!::isatty(::fileno(stdout)) ||
|
||||||
|
!::isatty(::fileno(stderr)) ||
|
||||||
|
::getenv("NO_COLOR") != NULL;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
cli.add_flag("-q,--quiet", opts.quiet, "Hide non-essential output");
|
||||||
|
cli.add_flag("-v,--verbose", opts.verbose, "Show verbose output");
|
||||||
|
cli.add_flag("--log-stderr", opts.log_stderr, "Log to stderr");
|
||||||
|
cli.add_flag("--nocolor", opts.nocolor, "Don't show ANSI colors")
|
||||||
|
->default_val(default_no_color)
|
||||||
|
->default_str(default_no_color ? "<true>" : "<false>");
|
||||||
|
cli.add_flag("-d,--debug", opts.debug, "Run in debug mode")
|
||||||
|
->group(""/*always hide*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Options>
|
||||||
|
Options::make(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
Options opts{};
|
||||||
|
CLI::App app{"mu mail indexer/searcher", "mu"};
|
||||||
|
|
||||||
|
app.description(R"(mu mail indexer/searcher
|
||||||
|
Copyright (C) 2008-2022 Dirk-Jan C. Binnema
|
||||||
|
|
||||||
|
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
)");
|
||||||
|
app.set_version_flag("-V,--version", PACKAGE_VERSION);
|
||||||
|
app.set_help_flag("-h,--help", "Show help informmation");
|
||||||
|
app.set_help_all_flag("--help-all");
|
||||||
|
app.require_subcommand(0, 1);
|
||||||
|
|
||||||
|
add_global_options(app, opts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* subcommands
|
||||||
|
*
|
||||||
|
* we keep around a map of the subcommand pointers, so we can
|
||||||
|
* easily find the chosen one (if any) later.
|
||||||
|
*/
|
||||||
|
for (auto&& cmdinfo: SubCommandInfos) {
|
||||||
|
//const auto cmdtype = cmdinfo.first;
|
||||||
|
const auto name{std::string{cmdinfo.second.name}};
|
||||||
|
const auto help{std::string{cmdinfo.second.help}};
|
||||||
|
const auto setup{cmdinfo.second.setup_func};
|
||||||
|
const auto cat{category(cmdinfo.first)};
|
||||||
|
|
||||||
|
if (!setup)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto sub = app.add_subcommand(name, help);
|
||||||
|
setup(*sub, opts);
|
||||||
|
|
||||||
|
/* allow global options _after_ subcommand as well;
|
||||||
|
* this is for backward compat with the older
|
||||||
|
* command-line parsing */
|
||||||
|
sub->fallthrough(true);
|
||||||
|
|
||||||
|
/* store commands get the '--muhome' parameter as well */
|
||||||
|
if (cat == Category::NeedsReadOnlyStore ||
|
||||||
|
cat == Category::NeedsWritableStore)
|
||||||
|
sub->add_option("--muhome",
|
||||||
|
opts.muhome, "Specify alternative mu directory")
|
||||||
|
->envname("MUHOME")
|
||||||
|
->type_name("<dir>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add scripts (if supported) as semi-subscommands as well */
|
||||||
|
const auto scripts = add_scripts(app, opts);
|
||||||
|
|
||||||
|
try {
|
||||||
|
app.parse(argc, argv);
|
||||||
|
|
||||||
|
// find the chosen sub command, if any.
|
||||||
|
for (auto&& cmdinfo: SubCommandInfos) {
|
||||||
|
if (cmdinfo.first == SubCommand::Script)
|
||||||
|
continue; // not a _real_ subcommand.
|
||||||
|
const auto name{std::string{cmdinfo.second.name}};
|
||||||
|
if (app.got_subcommand(name)) {
|
||||||
|
opts.sub_command = cmdinfo.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, perhaps it's a script?
|
||||||
|
if (!opts.sub_command) {
|
||||||
|
for (auto&& info: scripts) { // find the chosen script, if any.
|
||||||
|
if (app.got_subcommand(info.name)) {
|
||||||
|
opts.sub_command = SubCommand::Script;
|
||||||
|
opts.script.name = info.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if nothing else, try "help"
|
||||||
|
if (opts.sub_command.value_or(SubCommand::Help) == SubCommand::Help)
|
||||||
|
return cmd_help(app, opts);
|
||||||
|
|
||||||
|
} catch (const CLI::CallForHelp& cfh) {
|
||||||
|
std::cout << app.help() << std::flush;
|
||||||
|
} catch (const CLI::CallForAllHelp& cfah) {
|
||||||
|
std::cout << app.help("", CLI::AppFormatMode::All) << std::flush;
|
||||||
|
} catch (const CLI::CallForVersion&) {
|
||||||
|
std::cout << "version " << PACKAGE_VERSION << "\n";
|
||||||
|
} catch (const CLI::ParseError& pe) {
|
||||||
|
return Err(Error::Code::InvalidArgument, "%s", pe.what());
|
||||||
|
} catch (...) {
|
||||||
|
return Err(Error::Code::Internal, "error parsing arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(std::move(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
Category
|
||||||
|
Options::category(Options::SubCommand sub)
|
||||||
|
{
|
||||||
|
for (auto&& item: SubCommandInfos)
|
||||||
|
if (item.first == sub)
|
||||||
|
return item.second.category;
|
||||||
|
|
||||||
|
return Category::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* trust but verify
|
||||||
|
*/
|
||||||
|
|
||||||
|
static constexpr bool
|
||||||
|
validate_subcommand_ids()
|
||||||
|
{
|
||||||
|
for (auto u = 0U; u != SubCommandInfos.size(); ++u)
|
||||||
|
if (static_cast<size_t>(SubCommandInfos.at(u).first) != u)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tests... also build as runtime-tests, so we can get coverage info
|
||||||
|
*/
|
||||||
|
#ifdef BUILD_TESTS
|
||||||
|
#define static_assert g_assert_true
|
||||||
|
#endif /*BUILD_TESTS*/
|
||||||
|
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
static void
|
||||||
|
test_ids()
|
||||||
|
{
|
||||||
|
static_assert(validate_subcommand_ids());
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef BUILD_TESTS
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
mu_test_init(&argc, &argv);
|
||||||
|
|
||||||
|
g_test_add_func("/options/ids", test_ids);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
|
#endif /*BUILD_TESTS*/
|
||||||
278
mu/mu-options.hh
Normal file
278
mu/mu-options.hh
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
|
**
|
||||||
|
** This program is free software; you can redistribute it and/or modify it
|
||||||
|
** under the terms of the GNU General Public License as published by the
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MU_OPTIONS_HH__
|
||||||
|
#define MU_OPTIONS_HH__
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utils/mu-option.hh>
|
||||||
|
#include <utils/mu-result.hh>
|
||||||
|
#include <utils/mu-utils.hh>
|
||||||
|
#include <message/mu-fields.hh>
|
||||||
|
#include <mu-script.hh>
|
||||||
|
#include <ctime>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
/* command-line options for Mu */
|
||||||
|
namespace Mu {
|
||||||
|
struct Options {
|
||||||
|
|
||||||
|
using OptSize = Option<std::size_t>;
|
||||||
|
using SizeVec = std::vector<std::size_t>;
|
||||||
|
using OptTStamp = Option<std::time_t>;
|
||||||
|
using OptFieldId = Option<Field::Id>;
|
||||||
|
using StringVec = std::vector<std::string>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* general options
|
||||||
|
*/
|
||||||
|
bool quiet; /**< don't give any output */
|
||||||
|
bool debug; /**< log debug-level info */
|
||||||
|
bool version; /**< request mu version */
|
||||||
|
bool log_stderr; /**< log to stderr */
|
||||||
|
bool nocolor; /**< don't use use ansi-colors */
|
||||||
|
bool verbose; /**< verbose output */
|
||||||
|
std::string muhome; /**< alternative mu dir */
|
||||||
|
|
||||||
|
enum struct SubCommand {
|
||||||
|
Add, Cfind, Extract, Fields, Find, Help, Index,Info, Init, Mkdir,
|
||||||
|
Remove, Script, Server, Verify, View/*must be last*/
|
||||||
|
};
|
||||||
|
static constexpr std::size_t SubCommandNum =
|
||||||
|
1 + static_cast<std::size_t>(SubCommand::View);
|
||||||
|
static constexpr std::array<SubCommand, SubCommandNum> SubCommands = {{
|
||||||
|
SubCommand::Add,
|
||||||
|
SubCommand::Cfind,
|
||||||
|
SubCommand::Extract,
|
||||||
|
SubCommand::Fields,
|
||||||
|
SubCommand::Find,
|
||||||
|
SubCommand::Help,
|
||||||
|
SubCommand::Index,
|
||||||
|
SubCommand::Info,
|
||||||
|
SubCommand::Init,
|
||||||
|
SubCommand::Mkdir,
|
||||||
|
SubCommand::Remove,
|
||||||
|
SubCommand::Script,
|
||||||
|
SubCommand::Server,
|
||||||
|
SubCommand::Verify,
|
||||||
|
SubCommand::View
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
Option<SubCommand> sub_command; /**< The chosen sub-command, if any. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add
|
||||||
|
*/
|
||||||
|
struct Add {
|
||||||
|
StringVec files; /**< field to add */
|
||||||
|
} add;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cfind
|
||||||
|
*/
|
||||||
|
struct Cfind {
|
||||||
|
enum struct Format { Plain, MuttAlias, MuttAddressBook,
|
||||||
|
Wanderlust, OrgContact, Bbdb, Csv, Debug };
|
||||||
|
Format format; /**< Output format */
|
||||||
|
bool personal; /**< only show personal contacts */
|
||||||
|
OptTStamp after; /**< only last seen after tstamp */
|
||||||
|
OptSize maxnum; /**< maximum number of results */
|
||||||
|
std::string rx_pattern; /**< contact regexp to match */
|
||||||
|
} cfind;
|
||||||
|
|
||||||
|
|
||||||
|
struct Crypto {
|
||||||
|
bool auto_retrieve; /**< auto-retrieve keys */
|
||||||
|
bool decrypt; /**< decrypt */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract
|
||||||
|
*/
|
||||||
|
struct Extract: public Crypto {
|
||||||
|
std::string message; /**< path to message file */
|
||||||
|
bool save_all; /**< extract all parts */
|
||||||
|
bool save_attachments; /**< extract all attachment parts */
|
||||||
|
SizeVec parts; /**< parts to save / open */
|
||||||
|
std::string targetdir{}; /**< where to save attachments */
|
||||||
|
bool overwrite; /**< overwrite same-named files */
|
||||||
|
bool play; /**< try to 'play' attachment */
|
||||||
|
std::string filename_rx; /**< Filename rx to save */
|
||||||
|
} extract;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fields
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find
|
||||||
|
*/
|
||||||
|
struct Find {
|
||||||
|
std::string fields; /**< fields to show in output */
|
||||||
|
Field::Id sortfield; /**< field to sort by */
|
||||||
|
OptSize maxnum; /**< max # of entries to print */
|
||||||
|
bool reverse; /**< sort in revers order (z->a) */
|
||||||
|
bool threads; /**< show message threads */
|
||||||
|
bool clearlinks; /**< clear linksdir first */
|
||||||
|
std::string linksdir; /**< directory for links */
|
||||||
|
OptSize summary_len; /**< max # of lines for summary */
|
||||||
|
std::string bookmark; /**< use bookmark */
|
||||||
|
|
||||||
|
enum struct Format { Plain, Links, Xml, Json, Sexp, XQuery, MQuery, Exec };
|
||||||
|
Format format; /**< Output format */
|
||||||
|
std::string exec; /**< cmd to execute on matches */
|
||||||
|
bool skip_dups; /**< show only first with msg id */
|
||||||
|
bool include_related; /**< included related messages */
|
||||||
|
/**< for find and cind */
|
||||||
|
OptTStamp after; /**< only last seen after T */
|
||||||
|
bool auto_retrieve; /**< assume we're online */
|
||||||
|
bool decrypt; /**< try to decrypt the body */
|
||||||
|
|
||||||
|
StringVec query; /**< search query */
|
||||||
|
} find;
|
||||||
|
|
||||||
|
struct Help {
|
||||||
|
std::string command; /**< Help parameter */
|
||||||
|
} help;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Index
|
||||||
|
*/
|
||||||
|
struct Index {
|
||||||
|
bool nocleanup; /**< don't cleanup del'd mails */
|
||||||
|
bool lazycheck; /**< don't check uptodate dirs */
|
||||||
|
} index;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Info
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Init
|
||||||
|
*/
|
||||||
|
struct Init {
|
||||||
|
std::string maildir; /**< where the mails are */
|
||||||
|
StringVec my_addresses; /**< personal e-mail addresses */
|
||||||
|
OptSize max_msg_size; /**< max size for message files */
|
||||||
|
OptSize batch_size; /**< db transaction batch size */
|
||||||
|
bool reinit; /**< re-initialize */
|
||||||
|
} init;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mkdir
|
||||||
|
*/
|
||||||
|
struct Mkdir {
|
||||||
|
StringVec dirs; /**< Dir(s) to create */
|
||||||
|
mode_t mode; /**< Mode for the maildir */
|
||||||
|
} mkdir;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove
|
||||||
|
*/
|
||||||
|
struct Remove {
|
||||||
|
StringVec files; /**< Files to remove */
|
||||||
|
} remove;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scripts (i.e., finding scriot)
|
||||||
|
*/
|
||||||
|
struct Script {
|
||||||
|
std::string name; /**< name of script */
|
||||||
|
StringVec params; /**< script params */
|
||||||
|
} script;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Server
|
||||||
|
*/
|
||||||
|
struct Server {
|
||||||
|
bool commands; /**< dump docs for commands */
|
||||||
|
std::string eval; /**< command to evaluate */
|
||||||
|
} server;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify
|
||||||
|
*/
|
||||||
|
struct Verify: public Crypto {
|
||||||
|
StringVec files; /**< message files to verify */
|
||||||
|
} verify;
|
||||||
|
/*
|
||||||
|
* View
|
||||||
|
*/
|
||||||
|
struct View: public Crypto {
|
||||||
|
bool terminate; /**< add \f between msgs in view */
|
||||||
|
OptSize summary_len; /**< max # of lines for summary */
|
||||||
|
|
||||||
|
enum struct Format { Plain, Sexp };
|
||||||
|
Format format; /**< output format*/
|
||||||
|
|
||||||
|
StringVec files; /**< Message file(s) */
|
||||||
|
} view;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Options structure fo the given command-line arguments.
|
||||||
|
*
|
||||||
|
* @param argc argc
|
||||||
|
* @param argv argc
|
||||||
|
*
|
||||||
|
* @return Options, or an Error
|
||||||
|
*/
|
||||||
|
static Result<Options> make(int argc, char *argv[]);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Different commands need different things
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
enum struct Category {
|
||||||
|
None,
|
||||||
|
NeedsReadOnlyStore,
|
||||||
|
NeedsWritableStore,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the category for some subcommand
|
||||||
|
*
|
||||||
|
* @param sub subcommand
|
||||||
|
*
|
||||||
|
* @return the category
|
||||||
|
*/
|
||||||
|
static Category category(SubCommand sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get some well-known Path
|
||||||
|
*
|
||||||
|
* @param path the Path to find
|
||||||
|
*
|
||||||
|
* @return the path name
|
||||||
|
*/
|
||||||
|
std::string runtime_path(RuntimePath path) const {
|
||||||
|
return Mu::runtime_path(path, muhome);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namepace Mu
|
||||||
|
|
||||||
|
#endif /* MU_OPTIONS_HH__ */
|
||||||
91
mu/mu.cc
91
mu/mu.cc
@ -18,40 +18,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib-object.h>
|
#include <glib-object.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
|
|
||||||
#include "mu-config.hh"
|
|
||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-runtime.hh"
|
#include "mu-options.hh"
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
|
#include "utils/mu-logger.hh"
|
||||||
|
|
||||||
|
#include "mu-cmd.hh"
|
||||||
|
|
||||||
using namespace Mu;
|
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
|
static int
|
||||||
handle_result(const Result<void>& res, MuConfig* conf)
|
handle_result(const Result<void>& res, const Mu::Options& opts)
|
||||||
{
|
{
|
||||||
if (res)
|
if (res)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
using Color = MaybeAnsi::Color;
|
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.
|
// show the error and some help, but not if it's only a softerror.
|
||||||
if (!res.error().is_soft_error()) {
|
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.
|
// perhaps give some useful hint on how to solve it.
|
||||||
switch (res.error().code()) {
|
switch (res.error().code()) {
|
||||||
case Error::Code::InvalidArgument:
|
case Error::Code::InvalidArgument:
|
||||||
if (conf && mu_config_cmd_is_valid(conf->cmd))
|
|
||||||
mu_config_show_help(conf->cmd);
|
|
||||||
break;
|
break;
|
||||||
case Error::Code::StoreLock:
|
case Error::Code::StoreLock:
|
||||||
std::cerr << "Perhaps mu is already running?\n";
|
std::cerr << "Perhaps mu is already running?\n";
|
||||||
@ -88,38 +75,38 @@ handle_result(const Result<void>& res, MuConfig* conf)
|
|||||||
int
|
int
|
||||||
main(int argc, char* argv[])
|
main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
int rv{};
|
|
||||||
MuConfig *conf{};
|
|
||||||
GError* err{};
|
|
||||||
|
|
||||||
using Color = MaybeAnsi::Color;
|
|
||||||
MaybeAnsi col{conf ? !conf->nocolor : false};
|
|
||||||
|
|
||||||
setlocale(LC_ALL, "");
|
setlocale(LC_ALL, "");
|
||||||
|
|
||||||
conf = mu_config_init(&argc, &argv, &err);
|
/*
|
||||||
if (!conf) {
|
* read command-line options
|
||||||
std::cerr << col.fg(Color::Red) << "error" << col.reset() << ": "
|
*/
|
||||||
<< col.fg(Color::BrightYellow)
|
const auto opts{Options::make(argc, argv)};
|
||||||
<< (err ? err->message : "something went wrong") << "\n";
|
if (!opts) {
|
||||||
rv = 1;
|
std::cerr << "error: " << opts.error().what() << "\n";
|
||||||
goto cleanup;
|
return opts.error().exit_code();
|
||||||
} else if (conf->version) {
|
} else if (!opts->sub_command) {
|
||||||
show_version();
|
// nothing more to do.
|
||||||
goto cleanup;
|
return 0;
|
||||||
} 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"
|
* set up logging
|
||||||
<< col.reset();
|
*/
|
||||||
rv = 2;
|
Logger::Options lopts{Logger::Options::None};
|
||||||
} else
|
if (opts->log_stderr)
|
||||||
rv = handle_result(mu_cmd_execute(conf), conf);
|
lopts |= Logger::Options::StdOutErr;
|
||||||
|
if (opts->debug)
|
||||||
cleanup:
|
lopts |= Logger::Options::Debug;
|
||||||
g_clear_error(&err);
|
|
||||||
mu_config_uninit(conf);
|
const auto logger = Logger::make(opts->runtime_path(RuntimePath::LogFile),
|
||||||
mu_runtime_uninit();
|
lopts);
|
||||||
|
if (!logger) {
|
||||||
return rv;
|
std::cerr << "error:" << logger.error().what() << "\n";
|
||||||
|
return logger.error().exit_code();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* handle sub command
|
||||||
|
*/
|
||||||
|
return handle_result(mu_cmd_execute(*opts), *opts);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user