From 97c17254615c6ed24f8c35a90f9c78c0d8ba64c5 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Mon, 4 Apr 2022 23:48:33 +0300 Subject: [PATCH] message: implement conversion to sexp Like mu-msg-sexp, but for Mu::Message --- lib/message/meson.build | 1 + lib/message/mu-message-sexp.cc | 177 +++++++++++++++++++++++++++++++++ lib/message/mu-message.cc | 8 ++ lib/message/mu-message.hh | 30 +++++- 4 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 lib/message/mu-message-sexp.cc diff --git a/lib/message/meson.build b/lib/message/meson.build index 7f5c6d5d..4b530d89 100644 --- a/lib/message/meson.build +++ b/lib/message/meson.build @@ -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', diff --git a/lib/message/mu-message-sexp.cc b/lib/message/mu-message-sexp.cc new file mode 100644 index 00000000..9d0a18bc --- /dev/null +++ b/lib/message/mu-message-sexp.cc @@ -0,0 +1,177 @@ +/* +** Copyright (C) 2011-2022 Dirk-Jan C. Binnema +** +** 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 +#include + +#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&& 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("?", + 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()); +} diff --git a/lib/message/mu-message.cc b/lib/message/mu-message.cc index 8b3c9f7d..049b6e9c 100644 --- a/lib/message/mu-message.cc +++ b/lib/message/mu-message.cc @@ -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) { diff --git a/lib/message/mu-message.hh b/lib/message/mu-message.hh index 47b4a29e..f263faed 100644 --- a/lib/message/mu-message.hh +++ b/lib/message/mu-message.hh @@ -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 mdir);