message: implement conversion to sexp

Like mu-msg-sexp, but for Mu::Message
This commit is contained in:
Dirk-Jan C. Binnema 2022-04-04 23:48:33 +03:00
parent e9fdf7f01d
commit 97c1725461
4 changed files with 212 additions and 4 deletions

View File

@ -22,6 +22,7 @@ lib_mu_message=static_library(
'mu-message.hh',
'mu-message-part.cc',
'mu-message-part.hh',
'mu-message-sexp.cc',
'mu-contact.hh',
'mu-contact.cc',
'mu-document.cc',

View File

@ -0,0 +1,177 @@
/*
** Copyright (C) 2011-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 <string.h>
#include <ctype.h>
#include "mu-message.hh"
#include "mu-query-results.hh"
#include "utils/mu-str.h"
#include "mu-msg.hh"
#include "mu-msg-part.hh"
#include "mu-maildir.hh"
#include "utils/mu-utils.hh"
using namespace Mu;
static void
add_prop_nonempty(Sexp::List& list, std::string&& elm,
std::vector<std::string>&& items)
{
if (items.empty())
return;
Sexp::List elms;
for(auto&& item: items)
elms.add(Sexp::make_string(std::move(item)));
list.add_prop(std::move(elm), Sexp::make_list(std::move(elms)));
}
static void
add_prop_nonempty(Sexp::List& list, std::string&& name, std::string&& value)
{
if (!value.empty())
list.add_prop(std::move(name),
Sexp::make_string(std::move(value)));
}
static Mu::Sexp
make_contact_sexp(const Contact& contact)
{
return Sexp::make_list(
/* name */
Sexp::make_string(contact.name, true/*?nil*/),
/* dot */
Sexp::make_symbol("."),
/* email */
Sexp::make_string(contact.email));
}
static void
add_list_post(Sexp::List& list, const Message& message)
{
if (!message.has_mime_message())
return;
/* some mailing lists do not set the reply-to; see pull #1278. So for
* those cases, check the List-Post address and use that instead */
GMatchInfo* minfo;
GRegex* rx;
const auto list_post{message.header("List-Post")};
if (!list_post)
return;
rx = g_regex_new("<?mailto:([a-z0-9!@#$%&'*+-/=?^_`{|}~]+)>?",
G_REGEX_CASELESS, (GRegexMatchFlags)0, {});
g_return_if_fail(rx);
if (g_regex_match(rx, list_post->c_str(), (GRegexMatchFlags)0, &minfo)) {
auto address = (char*)g_match_info_fetch(minfo, 1);
list.add_prop(":list-post", make_contact_sexp(Contact{address}));
g_free(address);
}
g_match_info_free(minfo);
g_regex_unref(rx);
}
static void
add_contacts(Sexp::List& list, const Message& message)
{
auto add_contact_type = [&](const Contacts& contacts, std::string&& prop) {
Sexp::List clist;
seq_for_each(contacts, [&](auto&& c) { clist.add(make_contact_sexp(c)); });
if (!clist.empty())
list.add_prop(std::move(prop),
Sexp::make_list(std::move(clist)));
};
add_contact_type(message.from(),":from");
add_contact_type(message.to(), ":to");
add_contact_type(message.cc(), ":cc");
add_contact_type(message.bcc(), ":bcc");
// FIXME: reply-to.
}
static void
add_flags(Sexp::List& list, const Message& message)
{
Sexp::List flaglist;
const auto flags{message.flags()};
for (auto&& info: AllMessageFlagInfos)
if (any_of(flags & info.flag))
flaglist.add(Sexp::make_symbol_sv(info.name));
if (!flaglist.empty())
list.add_prop(":flags", Sexp::make_list(std::move(flaglist)));
}
static void
add_date_and_size(Sexp::List& items, const Message& message)
{
auto t{message.date()};
if (t != 0) {
Sexp::List dlist;
dlist.add(Sexp::make_number((unsigned)(t >> 16)));
dlist.add(Sexp::make_number((unsigned)(t & 0xffff)));
dlist.add(Sexp::make_number(0));
items.add_prop(":date", Sexp::make_list(std::move(dlist)));
}
auto size{message.size()};
if (size != 0)
items.add_prop(":size", Sexp::make_number(size));
}
Mu::Sexp::List
Message::to_sexp_list() const
{
Sexp::List items;
// if (docid != 0)
// items.add_prop(":docid", Sexp::make_number(docid));
add_prop_nonempty(items, ":subject", subject());
add_prop_nonempty(items, ":message-id", message_id());
add_prop_nonempty(items, ":mailing-list", mailing_list());
add_prop_nonempty(items, ":path", path());
add_prop_nonempty(items, ":maildir",maildir());
items.add_prop(":priority",
Sexp::make_symbol_sv(priority_name(priority())));
add_contacts(items, *this);
add_list_post(items, *this);
add_prop_nonempty(items, ":references", references());
add_prop_nonempty(items, ":tags", tags());
add_date_and_size(items, *this);
add_flags(items, *this);
return items;
}
Mu::Sexp
Message::to_sexp() const
{
return Sexp::make_list(to_sexp_list());
}

View File

@ -149,6 +149,14 @@ Message::unload_mime_message() const
{
priv_->mime_msg = Nothing;
}
bool
Message::has_mime_message() const
{
return !!priv_->mime_msg;
}
static Priority
get_priority(const MimeMessage& mime_msg)
{

View File

@ -32,6 +32,7 @@
#include "utils/mu-option.hh"
#include "utils/mu-result.hh"
#include "utils/mu-sexp.hh"
namespace Mu {
@ -147,7 +148,6 @@ public:
*/
Contacts bcc() const { return document().contacts_value(Field::Id::Bcc); }
/**
* Get the maildir this message lives in; ie, if the path is
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar
@ -235,11 +235,24 @@ public:
/*
* Below require a file-backed message, which is a relatively slow
* if there isn't one already ()
*
* Convert to Sexp
*/
/**
* convert the message to a Lisp symbolic expression (for further
* processing in e.g. emacs)
*
*
* @return a Mu::Sexp or a Mu::Sexp::List representing the message.
*/
Mu::Sexp::List to_sexp_list() const;
Mu::Sexp to_sexp() const;
/*
* Below require a file-backed message, which is a relatively slow
* if there isn't one already; see load_mime_message()
*
*/
/**
* Get the text body
@ -298,6 +311,15 @@ public:
*/
void unload_mime_message() const;
/**
* Has a (file-base) GMime message been loaded?
*
*
* @return true or false
*/
bool has_mime_message() const;
struct Private;
private:
Message(const std::string& path, Option<const std::string&> mdir);