cfind: rework, add support for json output
Update the old cfind code, and add json output support while doing so.
This commit is contained in:
@ -1,410 +1,323 @@
|
||||
/*
|
||||
** Copyright (C) 2011-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
** 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 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.
|
||||
** 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.
|
||||
** 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 <string>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mu-cmd.hh"
|
||||
#include "mu-contacts-cache.hh"
|
||||
|
||||
#include "utils/mu-util.h"
|
||||
#include "utils/mu-utils.hh"
|
||||
#include "utils/mu-error.hh"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <utils/mu-utils.hh>
|
||||
#include <utils/mu-regex.hh>
|
||||
#include <utils/mu-util.h>
|
||||
#include <utils/mu-option.hh>
|
||||
|
||||
using namespace Mu;
|
||||
|
||||
enum struct ItemType { Header, Footer, Normal };
|
||||
using OutputFunc = std::function<void(ItemType itype, Option<const Contact&>, const Options&)>;
|
||||
using OptContact = Option<const Contact&>;
|
||||
using Format = Options::Cfind::Format;
|
||||
|
||||
/**
|
||||
* macro to check whether the string is empty, ie. if it's NULL or
|
||||
* it's length is 0
|
||||
*
|
||||
* @param S a string
|
||||
*
|
||||
* @return TRUE if the string is empty, FALSE otherwise
|
||||
*/
|
||||
#define mu_str_is_empty(S) ((!(S)||!(*S))?TRUE:FALSE)
|
||||
|
||||
|
||||
/**
|
||||
* guess the last name for the given name; clearly,
|
||||
* this is just a rough guess for setting an initial value.
|
||||
*
|
||||
* @param name a name
|
||||
*
|
||||
* @return the last name, as a newly allocated string (free with
|
||||
* g_free)
|
||||
*/
|
||||
static gchar*
|
||||
guess_last_name(const char* name)
|
||||
// simplistic guess of first & last names, for setting
|
||||
// some initial value.
|
||||
static std::pair<std::string, std::string>
|
||||
guess_first_last_name(const std::string& name)
|
||||
{
|
||||
const gchar* lastsp;
|
||||
if (name.empty())
|
||||
return {};
|
||||
|
||||
if (!name)
|
||||
return g_strdup("");
|
||||
|
||||
lastsp = g_strrstr(name, " ");
|
||||
|
||||
return g_strdup(lastsp ? lastsp + 1 : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* guess the first name for the given name; clearly,
|
||||
* this is just a rough guess for setting an initial value.
|
||||
*
|
||||
* @param name a name
|
||||
*
|
||||
* @return the first name, as a newly allocated string (free with
|
||||
* g_free)
|
||||
*/
|
||||
static gchar*
|
||||
guess_first_name(const char* name)
|
||||
{
|
||||
const gchar* lastsp;
|
||||
|
||||
if (!name)
|
||||
return g_strdup("");
|
||||
|
||||
lastsp = g_strrstr(name, " ");
|
||||
|
||||
if (lastsp)
|
||||
return g_strndup(name, lastsp - name);
|
||||
const auto lastspc = name.find_last_of(' ');
|
||||
if (lastspc == name.npos)
|
||||
return { name, "" }; // no last name
|
||||
else
|
||||
return g_strdup(name);
|
||||
return { name.substr(0, lastspc), name.substr(lastspc + 1)};
|
||||
}
|
||||
|
||||
/**
|
||||
* guess some nick name for the given name; if we can determine an
|
||||
* first name, last name, the nick will be first name + the first char
|
||||
* of the last name. otherwise, it's just the first name. clearly,
|
||||
* this is just a rough guess for setting an initial value for nicks.
|
||||
*
|
||||
* @param name a name
|
||||
*
|
||||
* @return the guessed nick, as a newly allocated string (free with g_free)
|
||||
*/
|
||||
static gchar*
|
||||
cleanup_str(const char* str)
|
||||
|
||||
// candidate nick and a _count_ for that given nick, to uniquify them.
|
||||
static std::unordered_map<std::string, size_t> nicks;
|
||||
static std::string
|
||||
guess_nick(const Contact& contact)
|
||||
{
|
||||
gchar* s;
|
||||
const gchar* cur;
|
||||
unsigned i;
|
||||
auto cleanup = [](const std::string& str) {
|
||||
std::string clean;
|
||||
for (auto& c: str) // XXX: support non-ascii
|
||||
if (!::ispunct(c) && !::isspace(c))
|
||||
clean += c;
|
||||
return clean;
|
||||
};
|
||||
|
||||
if (mu_str_is_empty(str))
|
||||
return g_strdup("");
|
||||
auto nick = cleanup(std::invoke([&]()->std::string {
|
||||
|
||||
s = g_new0(char, strlen(str) + 1);
|
||||
// no name? use the user part from the addr
|
||||
if (contact.name.empty()) {
|
||||
const auto pos{contact.email.find('@')};
|
||||
if (pos == std::string::npos)
|
||||
return contact.email; // no '@'
|
||||
else
|
||||
return contact.email.substr(0, pos);
|
||||
}
|
||||
|
||||
for (cur = str, i = 0; *cur; ++cur) {
|
||||
if (ispunct(*cur) || isspace(*cur))
|
||||
continue;
|
||||
else
|
||||
s[i++] = *cur;
|
||||
const auto names{guess_first_last_name(contact.name)};
|
||||
/* if there's no last name, use first name as the nick */
|
||||
if (names.second.empty())
|
||||
return names.first;
|
||||
|
||||
char initial[7] = {};
|
||||
if (g_unichar_to_utf8(g_utf8_get_char(names.second.c_str()), initial) == 0) {
|
||||
/* couldn't we get an initial for the last name?
|
||||
* just use the first name*/
|
||||
return names.first;
|
||||
} else // prepend the initial
|
||||
return names.first + initial;
|
||||
}));
|
||||
|
||||
// uniquify.
|
||||
if (auto it = nicks.find(nick); it == nicks.cend())
|
||||
nicks.emplace(nick, 0);
|
||||
else {
|
||||
++it->second;
|
||||
nick = format("%s%zu", nick.c_str(), ++it->second);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static char*
|
||||
uniquify_nick(const char* nick, GHashTable* nicks)
|
||||
{
|
||||
guint u;
|
||||
|
||||
for (u = 2; u != 1000; ++u) {
|
||||
char* cand;
|
||||
cand = g_strdup_printf("%s%u", nick, u);
|
||||
if (!g_hash_table_contains(nicks, cand))
|
||||
return cand;
|
||||
}
|
||||
|
||||
return g_strdup(nick); /* if all else fails */
|
||||
}
|
||||
|
||||
static gchar*
|
||||
guess_nick(const char* name, GHashTable* nicks)
|
||||
{
|
||||
gchar *fname, *lname, *nick;
|
||||
gchar initial[7];
|
||||
|
||||
fname = guess_first_name(name);
|
||||
lname = guess_last_name(name);
|
||||
|
||||
/* if there's no last name, use first name as the nick */
|
||||
if (mu_str_is_empty(fname) || mu_str_is_empty(lname)) {
|
||||
g_free(lname);
|
||||
nick = fname;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
memset(initial, 0, sizeof(initial));
|
||||
/* couldn't we get an initial for the last name? */
|
||||
if (g_unichar_to_utf8(g_utf8_get_char(lname), initial) == 0) {
|
||||
g_free(lname);
|
||||
nick = fname;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
nick = g_strdup_printf("%s%s", fname, initial);
|
||||
g_free(fname);
|
||||
g_free(lname);
|
||||
|
||||
leave : {
|
||||
gchar* tmp;
|
||||
tmp = cleanup_str(nick);
|
||||
g_free(nick);
|
||||
nick = tmp;
|
||||
}
|
||||
|
||||
if (g_hash_table_contains(nicks, nick)) {
|
||||
char* tmp;
|
||||
tmp = uniquify_nick(nick, nicks);
|
||||
g_free(nick);
|
||||
nick = tmp;
|
||||
}
|
||||
|
||||
g_hash_table_add(nicks, g_strdup(nick));
|
||||
|
||||
return nick;
|
||||
}
|
||||
|
||||
using Format = Options::Cfind::Format;
|
||||
|
||||
static void
|
||||
print_header(Format format)
|
||||
output_plain(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
switch (format) {
|
||||
case Format::Bbdb:
|
||||
g_print(";; -*-coding: utf-8-emacs;-*-\n"
|
||||
";;; file-version: 6\n");
|
||||
break;
|
||||
case Format::MuttAddressBook:
|
||||
g_print("Matching addresses in the mu database:\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!contact)
|
||||
return;
|
||||
|
||||
const auto col1{opts.nocolor ? "" : MU_COLOR_MAGENTA};
|
||||
const auto col2{opts.nocolor ? "" : MU_COLOR_GREEN};
|
||||
const auto coldef{opts.nocolor ? "" : MU_COLOR_DEFAULT};
|
||||
|
||||
mu_util_print_encoded("%s%s%s%s%s%s%s\n",
|
||||
col1,
|
||||
contact->name.c_str(),
|
||||
coldef,
|
||||
contact->name.empty() ? "" : " ",
|
||||
col2,
|
||||
contact->email.c_str(),
|
||||
coldef);
|
||||
}
|
||||
|
||||
static void
|
||||
each_contact_bbdb(const std::string& email, const std::string& name, time_t tstamp)
|
||||
output_mutt_alias(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
char *fname, *lname;
|
||||
if (!contact)
|
||||
return;
|
||||
|
||||
fname = guess_first_name(name.c_str());
|
||||
lname = guess_last_name(name.c_str());
|
||||
const auto nick{guess_nick(*contact)};
|
||||
mu_util_print_encoded("alias %s %s <%s>\n", nick.c_str(),
|
||||
contact->name.c_str(), contact->email.c_str());
|
||||
}
|
||||
|
||||
const auto now{time_to_string("%Y-%m-%d", time(NULL))};
|
||||
const auto timestamp{time_to_string("%Y-%m-%d", tstamp)};
|
||||
static void
|
||||
output_mutt_address_book(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
if (itype == ItemType::Header)
|
||||
g_print ("Matching addresses in the mu database:\n");
|
||||
|
||||
if (!contact)
|
||||
return;
|
||||
|
||||
mu_util_print_encoded("%s\t%s\t\n",
|
||||
contact->email.c_str(),
|
||||
contact->name.c_str());
|
||||
}
|
||||
|
||||
static void
|
||||
output_wanderlust(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
if (!contact || contact->name.empty())
|
||||
return;
|
||||
|
||||
auto nick=guess_nick(*contact);
|
||||
|
||||
mu_util_print_encoded("%s \"%s\" \"%s\"\n",
|
||||
contact->email.c_str(),
|
||||
nick.c_str(),
|
||||
contact->name.c_str());
|
||||
}
|
||||
|
||||
static void
|
||||
output_org_contact(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
if (!contact || contact->name.empty())
|
||||
return;
|
||||
|
||||
mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n",
|
||||
contact->name.c_str(),
|
||||
contact->email.c_str());
|
||||
}
|
||||
|
||||
static void
|
||||
output_bbdb(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
if (itype == ItemType::Header)
|
||||
g_print (";; -*-coding: utf-8-emacs;-*-\n"
|
||||
";;; file-version: 6\n");
|
||||
if (!contact)
|
||||
return;
|
||||
|
||||
const auto names{guess_first_last_name(contact->name)};
|
||||
const auto now{time_to_string("%Y-%m-%d", ::time(NULL))};
|
||||
const auto timestamp{time_to_string("%Y-%m-%d", contact->message_date)};
|
||||
|
||||
g_print("[\"%s\" \"%s\" nil nil nil nil (\"%s\") "
|
||||
"((creation-date . \"%s\") (time-stamp . \"%s\")) nil]\n",
|
||||
fname,
|
||||
lname,
|
||||
email.c_str(),
|
||||
names.first.c_str(),
|
||||
names.second.c_str(),
|
||||
contact->email.c_str(),
|
||||
now.c_str(),
|
||||
timestamp.c_str());
|
||||
|
||||
g_free(fname);
|
||||
g_free(lname);
|
||||
}
|
||||
|
||||
static void
|
||||
each_contact_mutt_alias(const std::string& email,
|
||||
const std::string& name,
|
||||
GHashTable* nicks)
|
||||
output_csv(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
if (name.empty())
|
||||
if (!contact)
|
||||
return;
|
||||
|
||||
char* nick = guess_nick(name.c_str(), nicks);
|
||||
mu_util_print_encoded("alias %s %s <%s>\n", nick, name.c_str(), email.c_str());
|
||||
|
||||
g_free(nick);
|
||||
mu_util_print_encoded("%s,%s\n",
|
||||
contact->name.empty() ? "" : Mu::quote(contact->name).c_str(),
|
||||
Mu::quote(contact->email).c_str());
|
||||
}
|
||||
|
||||
static void
|
||||
each_contact_wl(const std::string& email,
|
||||
const std::string& name,
|
||||
GHashTable* nicks)
|
||||
output_json(ItemType itype, OptContact contact, const Options& opts)
|
||||
{
|
||||
if (name.empty())
|
||||
return;
|
||||
if (itype == ItemType::Header)
|
||||
g_print("[\n");
|
||||
if (contact) {
|
||||
g_print("%s", itype == ItemType::Header ? "" : ",\n");
|
||||
g_print (" {\n");
|
||||
|
||||
char* nick = guess_nick(name.c_str(), nicks);
|
||||
mu_util_print_encoded("%s \"%s\" \"%s\"\n", email.c_str(), nick, name.c_str());
|
||||
g_free(nick);
|
||||
}
|
||||
|
||||
static void
|
||||
print_plain(const std::string& email, const std::string& name, bool color)
|
||||
{
|
||||
if (!name.empty()) {
|
||||
if (color)
|
||||
::fputs(MU_COLOR_MAGENTA, stdout);
|
||||
mu_util_fputs_encoded(name.c_str(), stdout);
|
||||
::fputs(" ", stdout);
|
||||
const std::string name = contact->name.empty() ? "null" : Mu::quote(contact->name);
|
||||
mu_util_print_encoded(
|
||||
" \"email\" : \"%s\",\n"
|
||||
" \"name\" : %s,\n"
|
||||
" \"display\" : %s,\n"
|
||||
" \"last-seen\" : %" PRId64 ",\n"
|
||||
" \"last-seen-iso\" : \"%s\",\n"
|
||||
" \"personal\" : %s,\n"
|
||||
" \"frequency\" : %" PRId64 "\n",
|
||||
contact->email.c_str(),
|
||||
name.c_str(),
|
||||
Mu::quote(contact->display_name(true)).c_str(),
|
||||
contact->message_date,
|
||||
time_to_string("%FT%TZ", contact->message_date).c_str(),
|
||||
contact->personal ? "true" : "false",
|
||||
contact->frequency);
|
||||
g_print (" }");
|
||||
}
|
||||
|
||||
if (color)
|
||||
::fputs(MU_COLOR_GREEN, stdout);
|
||||
|
||||
mu_util_fputs_encoded(email.c_str(), stdout);
|
||||
|
||||
if (color)
|
||||
fputs(MU_COLOR_DEFAULT, stdout);
|
||||
|
||||
fputs("\n", stdout);
|
||||
if (itype == ItemType::Footer)
|
||||
g_print("\n]\n");
|
||||
}
|
||||
|
||||
struct ECData {
|
||||
Format format;
|
||||
gboolean color, personal;
|
||||
time_t after;
|
||||
GRegex* rx;
|
||||
GHashTable* nicks;
|
||||
size_t maxnum;
|
||||
size_t n;
|
||||
};
|
||||
|
||||
static void
|
||||
each_contact(const Mu::Contact& ci, ECData& ecdata)
|
||||
static OutputFunc
|
||||
find_output_func(Format format)
|
||||
{
|
||||
if (ecdata.personal && !ci.personal)
|
||||
return;
|
||||
|
||||
if (ci.message_date < ecdata.after)
|
||||
return;
|
||||
|
||||
if (ecdata.rx &&
|
||||
!g_regex_match(ecdata.rx, ci.email.c_str(), (GRegexMatchFlags)0, NULL) &&
|
||||
!g_regex_match(ecdata.rx,
|
||||
ci.name.empty() ? "" : ci.name.c_str(),
|
||||
(GRegexMatchFlags)0,
|
||||
NULL))
|
||||
return;
|
||||
|
||||
++ecdata.n;
|
||||
|
||||
switch (ecdata.format) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic error "-Wswitch"
|
||||
switch(format) {
|
||||
case Format::Plain:
|
||||
return output_plain;
|
||||
case Format::MuttAlias:
|
||||
each_contact_mutt_alias(ci.email, ci.name, ecdata.nicks);
|
||||
break;
|
||||
return output_mutt_alias;
|
||||
case Format::MuttAddressBook:
|
||||
mu_util_print_encoded("%s\t%s\t\n", ci.email.c_str(), ci.name.c_str());
|
||||
break;
|
||||
return output_mutt_address_book;
|
||||
case Format::Wanderlust:
|
||||
each_contact_wl(ci.email, ci.name, ecdata.nicks);
|
||||
break;
|
||||
return output_wanderlust;
|
||||
case Format::OrgContact:
|
||||
if (!ci.name.empty())
|
||||
mu_util_print_encoded("* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n",
|
||||
ci.name.c_str(),
|
||||
ci.email.c_str());
|
||||
break;
|
||||
return output_org_contact;
|
||||
case Format::Bbdb:
|
||||
each_contact_bbdb(ci.email, ci.name, ci.message_date);
|
||||
break;
|
||||
return output_bbdb;
|
||||
case Format::Csv:
|
||||
mu_util_print_encoded("%s,%s\n",
|
||||
ci.name.empty() ? "" : Mu::quote(ci.name).c_str(),
|
||||
Mu::quote(ci.email).c_str());
|
||||
break;
|
||||
case Format::Debug: {
|
||||
char datebuf[32];
|
||||
const auto mdate(static_cast<::time_t>(ci.message_date));
|
||||
::strftime(datebuf, sizeof(datebuf), "%F %T", ::gmtime(&mdate));
|
||||
g_print("%s\n\tname: %s\n\t%s\n\tpersonal: %s\n\tfreq: %zu\n"
|
||||
"\tlast-seen: %s\n",
|
||||
ci.email.c_str(),
|
||||
ci.name.empty() ? "<none>" : ci.name.c_str(),
|
||||
ci.display_name(true).c_str(),
|
||||
ci.personal ? "yes" : "no",
|
||||
ci.frequency,
|
||||
datebuf);
|
||||
}
|
||||
break;
|
||||
return output_csv;
|
||||
case Format::Json:
|
||||
return output_json;
|
||||
default:
|
||||
print_plain(ci.email, ci.name, ecdata.color);
|
||||
g_warning("unsupported format");
|
||||
return {};
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
static Result<void>
|
||||
run_cmd_cfind(const Mu::Store& store,
|
||||
const std::string& pattern,
|
||||
bool personal,
|
||||
time_t after,
|
||||
size_t maxnum,
|
||||
Format format,
|
||||
bool color)
|
||||
{
|
||||
ECData ecdata{};
|
||||
GError *err{};
|
||||
|
||||
memset(&ecdata, 0, sizeof(ecdata));
|
||||
|
||||
if (!pattern.empty()) {
|
||||
ecdata.rx = g_regex_new(
|
||||
pattern.c_str(),
|
||||
(GRegexCompileFlags)(G_REGEX_CASELESS | G_REGEX_OPTIMIZE),
|
||||
(GRegexMatchFlags)0, &err);
|
||||
|
||||
if (!ecdata.rx)
|
||||
return Err(Error::Code::Internal, &err, "invalid cfind regexp");
|
||||
}
|
||||
|
||||
ecdata.personal = personal;
|
||||
ecdata.n = 0;
|
||||
ecdata.after = after;
|
||||
ecdata.maxnum = maxnum;
|
||||
ecdata.format = format;
|
||||
ecdata.color = color;
|
||||
ecdata.nicks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
|
||||
print_header(format);
|
||||
|
||||
store.contacts_cache().for_each([&](const auto& ci) {
|
||||
each_contact(ci, ecdata);
|
||||
return ecdata.maxnum == 0 || ecdata.n < ecdata.maxnum;
|
||||
});
|
||||
g_hash_table_unref(ecdata.nicks);
|
||||
|
||||
if (ecdata.rx)
|
||||
g_regex_unref(ecdata.rx);
|
||||
|
||||
if (ecdata.n == 0)
|
||||
return Err(Error::Code::ContactNotFound, "no matching contacts found");
|
||||
else
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<void>
|
||||
Mu::mu_cmd_cfind(const Mu::Store& store, const Mu::Options& opts)
|
||||
{
|
||||
return run_cmd_cfind(store,
|
||||
opts.cfind.rx_pattern,
|
||||
opts.cfind.personal,
|
||||
opts.cfind.after.value_or(0),
|
||||
opts.cfind.maxnum.value_or(0),
|
||||
opts.cfind.format,
|
||||
!opts.nocolor);
|
||||
size_t num{};
|
||||
OutputFunc output = find_output_func(opts.cfind.format);
|
||||
if (!output)
|
||||
return Err(Error::Code::Internal,
|
||||
"missing output function");
|
||||
|
||||
// get the pattern regex, if any.
|
||||
Regex rx{};
|
||||
if (!opts.cfind.rx_pattern.empty()) {
|
||||
if (auto&& res = Regex::make(opts.cfind.rx_pattern,
|
||||
static_cast<GRegexCompileFlags>
|
||||
(G_REGEX_OPTIMIZE|G_REGEX_CASELESS)); !res)
|
||||
return Err(std::move(res.error()));
|
||||
else
|
||||
rx = res.value();
|
||||
}
|
||||
|
||||
nicks.clear();
|
||||
store.contacts_cache().for_each([&](const Contact& contact)->bool {
|
||||
|
||||
if (opts.cfind.maxnum && num > *opts.cfind.maxnum)
|
||||
return false; /* stop the loop */
|
||||
|
||||
if (!contact.has_valid_email())
|
||||
return true; /* next */
|
||||
|
||||
// filter for maxnum, personal & "after"
|
||||
if ((opts.cfind.personal && !contact.personal) ||
|
||||
(opts.cfind.after.value_or(0) > contact.message_date))
|
||||
return true; /* next */
|
||||
|
||||
// filter for regex, if any.
|
||||
if (rx) {
|
||||
if (!rx.matches(contact.name) && !rx.matches(contact.email))
|
||||
return true; /* next */
|
||||
}
|
||||
|
||||
/* seems we have a match! display it. */
|
||||
const auto itype{num == 0 ? ItemType::Header : ItemType::Normal};
|
||||
output(itype, contact, opts);
|
||||
++num;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (num == 0)
|
||||
return Err(Error::Code::ContactNotFound, "no matching contacts found");
|
||||
|
||||
output(ItemType::Footer, Nothing, opts);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user