mu-message-field: Implement

Implement Mu::MessageField, which replace MuMsgField with something a bit more
modern.
This commit is contained in:
Dirk-Jan C. Binnema 2022-02-23 09:29:37 +02:00
parent 724e0fd03a
commit 58af12add6
4 changed files with 587 additions and 0 deletions

View File

@ -65,6 +65,8 @@ lib_mu=static_library(
'mu-msg.hh',
'mu-message-contact.hh',
'mu-message-contact.cc',
'mu-message-fields.hh',
'mu-message-fields.cc',
'mu-message-flags.hh',
'mu-message-flags.cc',
'mu-message-priority.hh',

104
lib/mu-message-fields.cc Normal file
View File

@ -0,0 +1,104 @@
/*
** 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.
**
*/
#include "mu-message-fields.hh"
using namespace Mu;
std::optional<MessageField::Id>
message_field_id(const std::string& name_or_shortcut)
{
const auto it = std::find_if(
MessageFields.begin(), MessageFields.end(),
[&](auto&& field)->bool {
return field.name == name_or_shortcut ||
(name_or_shortcut.size() == 1 &&
name_or_shortcut[0] == field.shortcut);
});
if (it == MessageFields.end())
return std::nullopt;
else
return it->id;
}
/**
* compile-time checks
*/
constexpr bool
validate_message_field_ids()
{
for (auto id = 0U; id != MessageField::id_size(); ++id) {
const auto field_id = static_cast<MessageField::Id>(id);
if (message_field(field_id).id != field_id)
return false;
}
return true;
}
constexpr bool
validate_message_field_shortcuts()
{
for (auto id = 0U; id != MessageField::id_size(); ++id) {
const auto field_id = static_cast<MessageField::Id>(id);
const auto shortcut = message_field(field_id).shortcut;
if (shortcut != 0 &&
(shortcut < 'a' || shortcut > 'z'))
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_message_field_ids());
}
[[maybe_unused]]
static void
test_shortcuts()
{
static_assert(validate_message_field_shortcuts());
}
#ifdef BUILD_TESTS
int
main(int argc, char* argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/message/fields/ids", test_ids);
g_test_add_func("/message/fields/shortcuts", test_shortcuts);
return g_test_run();
}
#endif /*BUILD_TESTS*/

441
lib/mu-message-fields.hh Normal file
View File

@ -0,0 +1,441 @@
/*
** 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_MESSAGE_FIELDS_HH__
#define MU_MESSAGE_FIELDS_HH__
#include <cstdint>
#include <string_view>
#include <algorithm>
#include <array>
#include <optional>
#include <xapian.h>
#include <utils/mu-utils.hh>
namespace Mu {
struct MessageField {
/**
* Field Ids.
*
* Note, the Ids are also used as indices in the MessageFields array,
* so their numerical values must be 0...Count.
*
*/
enum struct Id {
/*
* first all the string-based ones
*/
Bcc = 0, /**< Blind Carbon-Copy */
BodyHtml, /**< HTML Body */
BodyText, /**< Text body */
Cc, /**< Carbon-Copy */
EmbeddedText, /**< Embedded text in message */
File, /**< Filename */
From, /**< Message sender */
Maildir, /**< Maildir path */
Mime, /**< MIME-Type */
MessageId, /**< Message Id */
Path, /**< File-system Path */
Subject, /**< Message subject */
To, /**< To: recipient */
Uid, /**< Unique id for message (based on path) */
/*
* string list items...
*/
References, /**< All references (incl. Reply-To:) */
Tags, /**< Message Tags */
/*
* then the numerical ones
*/
Date, /**< Message date */
Flags, /**< Message flags */
Priority, /**< Message priority */
Size, /**< Message size (in bytes) */
/* add new ones here... */
MailingList, /**< Mailing list */
ThreadId, /**< Thread Id */
/*
* <private>
*/
_count_ /**< Number of MessageFieldIds */
};
/**
* Get the number of Id values.
*
* @return the number.
*/
static constexpr size_t id_size()
{
return static_cast<size_t>(Id::_count_);
}
/**
* Field types
*
*/
enum struct Type {
String, /**< String */
StringList, /**< List of strings */
ByteSize, /**< Size in bytes */
TimeT, /**< A time_t value */
Integer, /**< An integer */
};
/**
* Field flags
* note: the differences for our purposes between a xapian field and a
* term: - there is only a single value for some item in per document
* (msg), ie. one value containing the list of To: addresses - there
* can be multiple terms, each containing e.g. one of the To:
* addresses - searching uses terms, but to display some field, it
* must be in the value (at least when using MuMsgIter)
*/
enum struct Flag {
GMime = 1 << 0,
/**< Field retrieved through gmime */
FullText = 1 << 1,
/**< Field-text is indexed in xapian (i.e., the text is processed */
Searchable = 1 << 2,
/**< Field is a searchable term */
Value = 1 << 3,
/**< Field value is stored (so the literal value can be retrieved) */
Contact = 1 << 4,
/**< field contains one or more * e-mail-addresses */
XapianBoolean = 1 << 5,
/**< use 'add_boolean_prefix' for Xapian queries; wildcards do NOT WORK for such
* fields */
DoNotCache = 1 << 6,
/**< don't cache this field in * the MuMsg cache */
Range = 1 << 7 /**< whether this is a range field (e.g., date, size)*/
};
/**
* Field members
*
*/
Id id; /**< Id of the message field */
Type type; /**< Type of the message field */
std::string_view name; /**< Name of the message field */
std::string_view description; /**< Decription of the message field */
std::string_view example_query; /**< Example query */
char shortcut; /**< Shortcut for the message field; a..z */
Flag flags; /**< Flags */
/**
* Convenience / helpers
*
*/
constexpr char xapian_prefix() const
{ /* xapian uses uppercase shortcuts; toupper is not constexpr */
return shortcut == 0 ? 0 : shortcut - ('a' - 'A');
}
constexpr Xapian::valueno value_no() const {
return static_cast<Xapian::valueno>(id);
}
constexpr bool is_numeric() const
{
return type == Type::ByteSize ||
type == Type::TimeT ||
type == Type::Integer;
}
};
MU_ENABLE_BITOPS(MessageField::Flag);
/**
* Sequence of _all_ message fields
*/
static constexpr std::array<MessageField, MessageField::id_size()>
MessageFields = {
{
// Bcc
{
MessageField::Id::Bcc,
MessageField::Type::String,
"bcc",
"Blind carbon-copy recipient",
"bcc:foo@example.com",
'h',
MessageField::Flag::GMime | MessageField::Flag::Contact |
MessageField::Flag::Value},
// HTML Body
{
MessageField::Id::BodyHtml,
MessageField::Type::String,
"body",
"Message html body",
{},
{},
MessageField::Flag::GMime | MessageField::Flag::DoNotCache},
// Body
{
MessageField::Id::BodyText,
MessageField::Type::String,
"body",
"Message plain-text body",
"body:capybara", // example
'b',
MessageField::Flag::GMime | MessageField::Flag::FullText |
MessageField::Flag::DoNotCache},
// Cc
{
MessageField::Id::Cc,
MessageField::Type::String,
"cc",
"Carbon-copy recipient",
"cc:quinn@example.com",
'c',
MessageField::Flag::GMime | MessageField::Flag::Contact |
MessageField::Flag::Value},
// Embed
{
MessageField::Id::EmbeddedText,
MessageField::Type::String,
"embed",
"Embedded text",
"embed:war OR embed:peace",
'e',
MessageField::Flag::GMime | MessageField::Flag::FullText |
MessageField::Flag::DoNotCache},
// File
{
MessageField::Id::File,
MessageField::Type::String,
"file",
"Attachment file name",
"file:/image\\.*.jpg/",
'j',
MessageField::Flag::GMime | MessageField::Flag::Searchable |
MessageField::Flag::DoNotCache},
// From
{
MessageField::Id::From,
MessageField::Type::String,
"from",
"Message sender",
"from:jimbo",
'f',
MessageField::Flag::GMime | MessageField::Flag::Contact |
MessageField::Flag::Value},
// Maildir
{
MessageField::Id::Maildir,
MessageField::Type::String,
"maildir",
"Maildir path for message",
"maildir:/private/archive",
'm',
MessageField::Flag::GMime | MessageField::Flag::Searchable |
MessageField::Flag::Value},
// MIME
{
MessageField::Id::Mime,
MessageField::Type::String,
"mime",
"Attachment MIME-type",
"mime:image/jpeg",
'y',
MessageField::Flag::Searchable},
// Message-ID
{
MessageField::Id::MessageId,
MessageField::Type::String,
"msgid",
"Attachment MIME-type",
"mime:image/jpeg",
'i',
MessageField::Flag::GMime |
MessageField::Flag::Searchable |
MessageField::Flag::Value},
// Path
{
MessageField::Id::Path,
MessageField::Type::String,
"path",
"File system path to message",
{},
'i',
MessageField::Flag::GMime |
MessageField::Flag::XapianBoolean |
MessageField::Flag::Value},
// Subject
{
MessageField::Id::Subject,
MessageField::Type::String,
"subject",
"Message subject",
"subject:wombat",
's',
MessageField::Flag::GMime |
MessageField::Flag::Searchable |
MessageField::Flag::Value |
MessageField::Flag::FullText},
// To
{
MessageField::Id::To,
MessageField::Type::String,
"To",
"Message recipient",
"to:flimflam@example.com",
't',
MessageField::Flag::GMime | MessageField::Flag::Contact |
MessageField::Flag::Value},
// UID (internal)
{
MessageField::Id::Uid,
MessageField::Type::String,
"uid",
"Message recipient",
{},
'u',
MessageField::Flag::Searchable},
// References
{
MessageField::Id::References,
MessageField::Type::StringList,
"refs",
"Message references to other messages",
{},
'r',
MessageField::Flag::GMime | MessageField::Flag::Value},
// Tags
{
MessageField::Id::Tags,
MessageField::Type::StringList,
"tag",
"Message tags",
"tag:projectx",
'x',
MessageField::Flag::GMime | MessageField::Flag::Searchable |
MessageField::Flag::Value},
// Date
{
MessageField::Id::Date,
MessageField::Type::TimeT,
"date",
"Message date",
"date:20220101..20220505",
'd',
MessageField::Flag::GMime | MessageField::Flag::Searchable |
MessageField::Flag::Value | MessageField::Flag::XapianBoolean |
MessageField::Flag::Range},
// Flags
{
MessageField::Id::Flags,
MessageField::Type::Integer,
"flag",
"Message properties",
"flag:unread",
'g',
MessageField::Flag::GMime | MessageField::Flag::Searchable |
MessageField::Flag::Value},
// Priority
{
MessageField::Id::Priority,
MessageField::Type::Integer,
"prio",
"Priority",
"prio:high",
'p',
MessageField::Flag::GMime |
MessageField::Flag::Searchable |
MessageField::Flag::Value},
// Size
{
MessageField::Id::Size,
MessageField::Type::ByteSize,
"size",
"Message size in bytes",
"size:1M..5M",
'z',
MessageField::Flag::GMime |
MessageField::Flag::Searchable |
MessageField::Flag::Value |
MessageField::Flag::Range},
// Mailing List
{
MessageField::Id::MailingList,
MessageField::Type::String,
"list",
"Mailing list (List-Id:)",
"list:mu-discuss.googlegroups.com",
'v',
MessageField::Flag::GMime | MessageField::Flag::Searchable |
MessageField::Flag::Value},
// ThreadId
{
MessageField::Id::ThreadId,
MessageField::Type::String,
"thread",
"Thread a message belongs to",
{},
'w',
MessageField::Flag::Searchable},
}};
/*
* Convenience
*/
/**
* Get the message field for the given Id.
*
* @param id of the message field
*
* @return ref of the message field.
*/
constexpr const MessageField&
message_field(MessageField::Id id)
{
return MessageFields.at(static_cast<size_t>(id));
}
/**
* Get the the message-field id for the given name or shortcut
*
* @param name_or_shortcut
*
* @return the message-field-id or nullopt.
*/
std::optional<MessageField::Id> message_field_id(const std::string& name_or_shortcut);
} // namespace Mu
#endif /* MU_MESSAGE_FIELDS_HH__ */

40
lib/mu-message.hh Normal file
View File

@ -0,0 +1,40 @@
/*
** 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_MESSAGE_HH__
#define MU_MESSAGE_HH__
#include "mu-message-contact.hh"
#include "mu-message-priority.hh"
#include "mu-message-flags.hh"
#include "mu-message-fields.hh"
namespace Mu {
namespace Message {
using Contact = MessageContact;
using Contacts = MessageContacts;
using Priority = MessagePriority;
using Flags = MessageFlags;
using Field = MessageField;
} // Message
} // Mu
#endif /* MU_MESSAGE_HH__ */