message:document/fields: update and tie down

Update many of the field flags; remove obsolete ones.

Ensure they are handled correctly in mu-document
This commit is contained in:
Dirk-Jan C. Binnema 2022-04-28 22:53:31 +03:00
parent b7a30c0a36
commit 9a8741f0dd
5 changed files with 157 additions and 150 deletions

View File

@ -36,23 +36,24 @@ constexpr char SepaChar1 = 0xfe;
constexpr char SepaChar2 = 0xff; constexpr char SepaChar2 = 0xff;
static void static void
add_index_term(Xapian::Document& doc, const Field& field, const std::string& val) add_search_term(Xapian::Document& doc, const Field& field, const std::string& val)
{ {
std::string flatval{utf8_flatten(val)}; if (field.is_normal_term()) {
Xapian::TermGenerator termgen; doc.add_term(field.xapian_term(val));
termgen.set_document(doc); } else if (field.is_boolean_term()) {
termgen.index_text(flatval);
}
static void
maybe_add_term(Xapian::Document& doc, const Field& field, const std::string& val)
{
if (field.is_normal_term())
doc.add_term(field.xapian_term());
else if (field.is_indexable_term()) {
add_index_term(doc, field, val);
} else if (field.is_boolean_term())
doc.add_boolean_term(field.xapian_term(val)); doc.add_boolean_term(field.xapian_term(val));
} else if (field.is_indexable_term()) {
Xapian::TermGenerator termgen;
termgen.set_document(doc);
termgen.index_text(utf8_flatten(val),1,field.xapian_term());
} else
throw std::logic_error("not a search term");
if (field.id == Field::Id::Tags)
for (auto tag = doc.termlist_begin(); tag != doc.termlist_end(); ++tag)
if ((*tag)[0] == 'X')
g_message("%u: %s", doc.get_docid(), (*tag).c_str());
} }
void void
@ -63,7 +64,8 @@ Document::add(Field::Id id, const std::string& val)
if (field.is_value()) if (field.is_value())
xdoc_.add_value(field.value_no(), val); xdoc_.add_value(field.value_no(), val);
maybe_add_term(xdoc_, field, val); if (field.is_searchable())
add_search_term(xdoc_, field, val);
} }
void void
@ -76,8 +78,10 @@ Document::add(Field::Id id, const std::vector<std::string>& vals)
if (field.is_value()) if (field.is_value())
xdoc_.add_value(field.value_no(), Mu::join(vals, SepaChar1)); xdoc_.add_value(field.value_no(), Mu::join(vals, SepaChar1));
std::for_each(vals.begin(), vals.end(), if (field.is_searchable())
[&](const auto& val) { maybe_add_term(xdoc_, field, val); }); std::for_each(vals.begin(), vals.end(),
[&](const auto& val) {
add_search_term(xdoc_, field, val); });
} }
@ -98,13 +102,19 @@ Document::add(Field::Id id, const Contacts& contacts)
const std::string sepa2(1, SepaChar2); const std::string sepa2(1, SepaChar2);
Xapian::TermGenerator termgen;
termgen.set_document(xdoc_);
for (auto&& contact: contacts) { for (auto&& contact: contacts) {
if (!contact.field_id || *contact.field_id != id) if (!contact.field_id || *contact.field_id != id)
continue; continue;
xdoc_.add_term(contact.email); xdoc_.add_term(field.xapian_term(contact.email));
if (!contact.name.empty()) if (!contact.name.empty())
add_index_term(xdoc_, field, contact.name); termgen.index_text(utf8_flatten(contact.name), 1,
field.xapian_term());
cvec.emplace_back(contact.email + sepa2 + contact.name); cvec.emplace_back(contact.email + sepa2 + contact.name);
} }
@ -134,26 +144,6 @@ Document::contacts_value(Field::Id id) const noexcept
return contacts; return contacts;
} }
static std::string
integer_to_string(int64_t val)
{
char buf[18];
buf[0] = 'f' + ::snprintf(buf + 1, sizeof(buf) - 1, "%" PRIx64, val);
return buf;
}
static int64_t
string_to_integer(const std::string& str)
{
if (str.empty())
return 0;
int64_t val{};
std::from_chars(str.c_str() + 1, str.c_str() + str.size(), val, 16);
return val;
}
void void
Document::add(Field::Id id, int64_t val) Document::add(Field::Id id, int64_t val)
{ {
@ -167,15 +157,16 @@ Document::add(Field::Id id, int64_t val)
const auto field{field_from_id(id)}; const auto field{field_from_id(id)};
if (field.is_value()) if (field.is_value())
xdoc_.add_value(field.value_no(), integer_to_string(val)); xdoc_.add_value(field.value_no(), to_lexnum(val));
/* terms are not supported for numerical fields */
} }
int64_t int64_t
Document::integer_value(Field::Id field_id) const noexcept Document::integer_value(Field::Id field_id) const noexcept
{ {
return string_to_integer(string_value(field_id)); if (auto&& v{string_value(field_id)}; v.empty())
return 0;
else
return from_lexnum(v);
} }
void void
@ -200,10 +191,11 @@ Document::add(Flags flags)
{ {
constexpr auto field{field_from_id(Field::Id::Flags)}; constexpr auto field{field_from_id(Field::Id::Flags)};
xdoc_.add_value(field.value_no(), integer_to_string(static_cast<int64_t>(flags))); xdoc_.add_value(field.value_no(), to_lexnum(static_cast<int64_t>(flags)));
flag_infos_for_each([&](auto&& flag_info) { flag_infos_for_each([&](auto&& flag_info) {
if (any_of(flag_info.flag & flags)) if (any_of(flag_info.flag & flags))
xdoc_.add_boolean_term(field.xapian_term(flag_info.shortcut_lower())); xdoc_.add_boolean_term(field.xapian_term(
flag_info.shortcut_lower()));
}); });
} }
@ -263,10 +255,13 @@ test_bcc()
Contact{"ringo@example.com", "Ringo", Field::Id::Bcc}, Contact{"ringo@example.com", "Ringo", Field::Id::Bcc},
}}; }};
doc.add(Field::Id::Bcc, contacts); doc.add(Field::Id::Bcc, contacts);
auto db = Xapian::InMemory::open();
TempDir tempdir;
auto db = Xapian::WritableDatabase(tempdir.path());
db.add_document(doc.xapian_document()); db.add_document(doc.xapian_document());
auto contacts2 = doc.contacts_value(Field::Id::Bcc);
assert_same_contacts(contacts, contacts2);
} }
} }

View File

@ -199,7 +199,7 @@ public:
* *
* @param field_id id of the contacts field to get * @param field_id id of the contacts field to get
* *
* @return an integer or 0 if not found. * @return a contacts list
*/ */
Contacts contacts_value(Field::Id id) const noexcept; Contacts contacts_value(Field::Id id) const noexcept;

View File

@ -22,24 +22,32 @@
using namespace Mu; using namespace Mu;
// Xapian does not like terms much longer than this
constexpr auto MaxTermLength = 240;
std::string std::string
Field::xapian_term(const std::string& s) const Field::xapian_term(const std::string& s) const
{ {
return std::string(1U, xapian_prefix()) + s; const auto start{std::string(1U, xapian_prefix())};
} if (const auto& size = s.size(); size == 0)
return start;
std::string std::string res{start};
Field::xapian_term(std::string_view sv) const res.reserve(s.size() + 10);
{
return std::string(1U, xapian_prefix()) + std::string{sv};
}
std::string
Field::xapian_term(char c) const
{
return std::string(1U, xapian_prefix()) + c;
}
/* slightly optimized common pure-ascii. */
if (G_LIKELY(g_str_is_ascii(s.c_str()))) {
res += s;
for (auto i = 1; res[i]; ++i)
res[i] = g_ascii_tolower(res[i]);
} else
res += utf8_flatten(s);
if (G_UNLIKELY(res.size() > MaxTermLength))
res.erase(MaxTermLength);
return res;
}
/** /**
* compile-time checks * compile-time checks
@ -58,13 +66,26 @@ validate_field_ids()
constexpr bool constexpr bool
validate_field_shortcuts() validate_field_shortcuts()
{ {
#ifdef BUILD_TESTS
std::array<size_t, 26> no_dups = {0};
#endif /*BUILD_TESTS*/
for (auto id = 0U; id != Field::id_size(); ++id) { for (auto id = 0U; id != Field::id_size(); ++id) {
const auto field_id = static_cast<Field::Id>(id); const auto field_id = static_cast<Field::Id>(id);
const auto shortcut = field_from_id(field_id).shortcut; const auto shortcut = field_from_id(field_id).shortcut;
if (shortcut != 0 && if (shortcut != 0 &&
(shortcut < 'a' || shortcut > 'z')) (shortcut < 'a' || shortcut > 'z'))
return false; return false;
#ifdef BUILD_TESTS
if (shortcut != 0) {
if (++no_dups[static_cast<size_t>(shortcut-'a')] > 1) {
g_critical("shortcut '%c' is duplicated",
shortcut);
return false;
}
}
#endif
} }
return true; return true;
} }
@ -94,8 +115,6 @@ validate_field_flags()
return true; return true;
} }
/* /*
* tests... also build as runtime-tests, so we can get coverage info * tests... also build as runtime-tests, so we can get coverage info
*/ */
@ -135,6 +154,20 @@ test_field_flags()
#ifdef BUILD_TESTS #ifdef BUILD_TESTS
static void
test_field_from_name()
{
g_assert_true(field_from_name("s")->id == Field::Id::Subject);
g_assert_true(field_from_name("subject")->id == Field::Id::Subject);
g_assert_false(!!field_from_name("8"));
g_assert_false(!!field_from_name(""));
g_assert_true(field_from_name("").value_or(field_from_id(Field::Id::Bcc)).id ==
Field::Id::Bcc);
}
static void static void
test_xapian_term() test_xapian_term()
{ {
@ -146,6 +179,10 @@ test_xapian_term()
assert_equal(field_from_id(Field::Id::From).xapian_term('x'), "Fx"); assert_equal(field_from_id(Field::Id::From).xapian_term('x'), "Fx");
assert_equal(field_from_id(Field::Id::To).xapian_term("boo"sv), "Tboo"); assert_equal(field_from_id(Field::Id::To).xapian_term("boo"sv), "Tboo");
auto s1 = field_from_id(Field::Id::Subject).xapian_term(std::string(MaxTermLength - 1, 'x'));
auto s2 = field_from_id(Field::Id::Subject).xapian_term(std::string(MaxTermLength, 'x'));
g_assert_cmpuint(s1.length(), ==, s2.length());
} }
int int
@ -155,6 +192,7 @@ main(int argc, char* argv[])
g_test_add_func("/message/fields/ids", test_ids); g_test_add_func("/message/fields/ids", test_ids);
g_test_add_func("/message/fields/shortcuts", test_shortcuts); g_test_add_func("/message/fields/shortcuts", test_shortcuts);
g_test_add_func("/message/fields/from-name", test_field_from_name);
g_test_add_func("/message/fields/prefix", test_prefix); g_test_add_func("/message/fields/prefix", test_prefix);
g_test_add_func("/message/fields/xapian-term", test_xapian_term); g_test_add_func("/message/fields/xapian-term", test_xapian_term);
g_test_add_func("/message/fields/flags", test_field_flags); g_test_add_func("/message/fields/flags", test_field_flags);

View File

@ -55,7 +55,6 @@ struct Field {
Path, /**< File-system Path */ Path, /**< File-system Path */
Subject, /**< Message subject */ Subject, /**< Message subject */
To, /**< To: recipient */ To, /**< To: recipient */
Uid, /**< Unique id for message (based on path) */
/* /*
* string list items... * string list items...
*/ */
@ -98,11 +97,12 @@ struct Field {
* *
*/ */
enum struct Type { enum struct Type {
String, /**< String */ String, /**< String */
StringList, /**< List of strings */ StringList, /**< List of strings */
ByteSize, /**< Size in bytes */ ContactList, /**< List of contacts */
TimeT, /**< A time_t value */ ByteSize, /**< Size in bytes */
Integer, /**< An integer */ TimeT, /**< A time_t value */
Integer, /**< An integer */
}; };
constexpr bool is_string() const { return type == Type::String; } constexpr bool is_string() const { return type == Type::String; }
@ -126,33 +126,29 @@ struct Field {
*/ */
enum struct Flag { enum struct Flag {
GMime = 1 << 0,
/**< Field retrieved through gmime */
/* /*
* Different kind of terms; at most one is true, * Different kind of terms; at most one is true,
* and cannot be combined with IsContact. Compile-time enforced. * and cannot be combined with IsContact. Compile-time enforced.
*/ */
NormalTerm = 1 << 2, NormalTerm = 1 << 0,
/**< Field is a searchable term */ /**< Field is a searchable term */
BooleanTerm = 1 << 5, BooleanTerm = 1 << 1,
/**< Field is a boolean search-term; wildcards do not work */ /**< Field is a boolean search-term (i.e. at most one per message);
IndexableTerm = 1 << 1, * wildcards do not work */
IndexableTerm = 1 << 2,
/**< Field has indexable text as term */ /**< Field has indexable text as term */
/* /*
* Contact flag cannot be combined with any of the term flags. * Contact flag cannot be combined with any of the term flags.
* This is compile-time enforced. * This is compile-time enforced.
*/ */
Contact = 1 << 4, Contact = 1 << 10,
/**< field contains one or more e-mail-addresses */ /**< field contains one or more e-mail-addresses */
Value = 1 << 11,
Value = 1 << 3,
/**< Field value is stored (so the literal value can be retrieved) */ /**< Field value is stored (so the literal value can be retrieved) */
DoNotCache = 1 << 6, DoNotCache = 1 << 20,
/**< don't cache this field in * the MuMsg cache */ /**< don't cache this field in * the MuMsg cache */
Range = 1 << 7 Range = 1 << 21
/**< whether this is a range field (e.g., date, size)*/ /**< whether this is a range field (e.g., date, size)*/
}; };
@ -160,12 +156,9 @@ struct Field {
return (static_cast<int>(some_flag) & static_cast<int>(flags)) != 0; return (static_cast<int>(some_flag) & static_cast<int>(flags)) != 0;
} }
constexpr bool is_gmime() const { return any_of(Flag::GMime); }
constexpr bool is_indexable_term() const { return any_of(Flag::IndexableTerm); } constexpr bool is_indexable_term() const { return any_of(Flag::IndexableTerm); }
constexpr bool is_boolean_term() const { return any_of(Flag::BooleanTerm); } constexpr bool is_boolean_term() const { return any_of(Flag::BooleanTerm); }
constexpr bool is_normal_term() const { return any_of(Flag::NormalTerm); } constexpr bool is_normal_term() const { return any_of(Flag::NormalTerm); }
constexpr bool is_searchable() const { return is_indexable_term() || constexpr bool is_searchable() const { return is_indexable_term() ||
is_boolean_term() || is_boolean_term() ||
is_normal_term(); } is_normal_term(); }
@ -174,9 +167,6 @@ struct Field {
constexpr bool is_contact() const { return any_of(Flag::Contact); } constexpr bool is_contact() const { return any_of(Flag::Contact); }
constexpr bool is_range() const { return any_of(Flag::Range); } constexpr bool is_range() const { return any_of(Flag::Range); }
constexpr bool do_not_cache() const { return any_of(Flag::DoNotCache); }
/** /**
* Field members * Field members
@ -200,9 +190,21 @@ struct Field {
return shortcut == 0 ? 0 : shortcut - ('a' - 'A'); return shortcut == 0 ? 0 : shortcut - ('a' - 'A');
} }
/**
* Get the xapian term; truncated to MaxTermLength and
* utf8-flattened.
*
* @param s
*
* @return the xapian term
*/
std::string xapian_term(const std::string& s="") const; std::string xapian_term(const std::string& s="") const;
std::string xapian_term(std::string_view sv) const; std::string xapian_term(std::string_view sv) const {
std::string xapian_term(char c) const; return xapian_term(std::string{sv});
}
std::string xapian_term(char c) const {
return xapian_term(std::string(1, c));
}
}; };
MU_ENABLE_BITOPS(Field::Flag); MU_ENABLE_BITOPS(Field::Flag);
@ -216,12 +218,11 @@ static constexpr std::array<Field, Field::id_size()>
// Bcc // Bcc
{ {
Field::Id::Bcc, Field::Id::Bcc,
Field::Type::String, Field::Type::ContactList,
"bcc", "bcc",
"Blind carbon-copy recipient", "Blind carbon-copy recipient",
"bcc:foo@example.com", "bcc:foo@example.com",
'h', 'h',
Field::Flag::GMime |
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value Field::Flag::Value
}, },
@ -233,8 +234,7 @@ static constexpr std::array<Field, Field::id_size()>
"Message html body", "Message html body",
{}, {},
{}, {},
Field::Flag::GMime | {}
Field::Flag::DoNotCache
}, },
// Body // Body
{ {
@ -244,22 +244,19 @@ static constexpr std::array<Field, Field::id_size()>
"Message plain-text body", "Message plain-text body",
"body:capybara", // example "body:capybara", // example
'b', 'b',
Field::Flag::GMime | Field::Flag::IndexableTerm,
Field::Flag::IndexableTerm |
Field::Flag::DoNotCache
}, },
// Cc // Cc
{ {
Field::Id::Cc, Field::Id::Cc,
Field::Type::String, Field::Type::ContactList,
"cc", "cc",
"Carbon-copy recipient", "Carbon-copy recipient",
"cc:quinn@example.com", "cc:quinn@example.com",
'c', 'c',
Field::Flag::GMime |
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value}, Field::Flag::Value
},
// Embed // Embed
{ {
Field::Id::EmbeddedText, Field::Id::EmbeddedText,
@ -268,9 +265,8 @@ static constexpr std::array<Field, Field::id_size()>
"Embedded text", "Embedded text",
"embed:war OR embed:peace", "embed:war OR embed:peace",
'e', 'e',
Field::Flag::GMime | Field::Flag::IndexableTerm
Field::Flag::IndexableTerm | },
Field::Flag::DoNotCache},
// File // File
{ {
Field::Id::File, Field::Id::File,
@ -279,21 +275,19 @@ static constexpr std::array<Field, Field::id_size()>
"Attachment file name", "Attachment file name",
"file:/image\\.*.jpg/", "file:/image\\.*.jpg/",
'j', 'j',
Field::Flag::GMime | Field::Flag::NormalTerm
Field::Flag::NormalTerm | },
Field::Flag::DoNotCache},
// From // From
{ {
Field::Id::From, Field::Id::From,
Field::Type::String, Field::Type::ContactList,
"from", "from",
"Message sender", "Message sender",
"from:jimbo", "from:jimbo",
'f', 'f',
Field::Flag::GMime |
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value}, Field::Flag::Value
},
// Maildir // Maildir
{ {
Field::Id::Maildir, Field::Id::Maildir,
@ -302,9 +296,9 @@ static constexpr std::array<Field, Field::id_size()>
"Maildir path for message", "Maildir path for message",
"maildir:/private/archive", "maildir:/private/archive",
'm', 'm',
Field::Flag::GMime | Field::Flag::BooleanTerm |
Field::Flag::NormalTerm | Field::Flag::Value
Field::Flag::Value}, },
// MIME // MIME
{ {
Field::Id::Mime, Field::Id::Mime,
@ -313,7 +307,8 @@ static constexpr std::array<Field, Field::id_size()>
"Attachment MIME-type", "Attachment MIME-type",
"mime:image/jpeg", "mime:image/jpeg",
'y', 'y',
Field::Flag::NormalTerm}, Field::Flag::NormalTerm
},
// Message-ID // Message-ID
{ {
Field::Id::MessageId, Field::Id::MessageId,
@ -322,9 +317,9 @@ static constexpr std::array<Field, Field::id_size()>
"Attachment MIME-type", "Attachment MIME-type",
"msgid:abc@123", "msgid:abc@123",
'i', 'i',
Field::Flag::GMime | Field::Flag::BooleanTerm |
Field::Flag::NormalTerm | Field::Flag::Value
Field::Flag::Value}, },
// Path // Path
{ {
Field::Id::Path, Field::Id::Path,
@ -332,11 +327,10 @@ static constexpr std::array<Field, Field::id_size()>
"path", "path",
"File system path to message", "File system path to message",
{}, {},
'p', 'l',
Field::Flag::GMime |
Field::Flag::BooleanTerm | Field::Flag::BooleanTerm |
Field::Flag::Value}, Field::Flag::Value
},
// Subject // Subject
{ {
Field::Id::Subject, Field::Id::Subject,
@ -345,32 +339,20 @@ static constexpr std::array<Field, Field::id_size()>
"Message subject", "Message subject",
"subject:wombat", "subject:wombat",
's', 's',
Field::Flag::GMime |
Field::Flag::Value | Field::Flag::Value |
Field::Flag::IndexableTerm}, Field::Flag::IndexableTerm
},
// To // To
{ {
Field::Id::To, Field::Id::To,
Field::Type::String, Field::Type::ContactList,
"to", "to",
"Message recipient", "Message recipient",
"to:flimflam@example.com", "to:flimflam@example.com",
't', 't',
Field::Flag::GMime |
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value Field::Flag::Value
}, },
// UID (internal)
{
Field::Id::Uid,
Field::Type::String,
"uid",
"Message recipient",
{},
'u',
Field::Flag::NormalTerm},
// References // References
{ {
Field::Id::References, Field::Id::References,
@ -379,7 +361,6 @@ static constexpr std::array<Field, Field::id_size()>
"Message references to other messages", "Message references to other messages",
{}, {},
'r', 'r',
Field::Flag::GMime |
Field::Flag::Value Field::Flag::Value
}, },
// Tags // Tags
@ -390,7 +371,6 @@ static constexpr std::array<Field, Field::id_size()>
"Message tags", "Message tags",
"tag:projectx", "tag:projectx",
'x', 'x',
Field::Flag::GMime |
Field::Flag::NormalTerm | Field::Flag::NormalTerm |
Field::Flag::Value Field::Flag::Value
}, },
@ -402,7 +382,6 @@ static constexpr std::array<Field, Field::id_size()>
"Message date", "Message date",
"date:20220101..20220505", "date:20220101..20220505",
'd', 'd',
Field::Flag::GMime |
Field::Flag::Value | Field::Flag::Value |
Field::Flag::Range Field::Flag::Range
}, },
@ -414,7 +393,6 @@ static constexpr std::array<Field, Field::id_size()>
"Message properties", "Message properties",
"flag:unread", "flag:unread",
'g', 'g',
Field::Flag::GMime |
Field::Flag::NormalTerm | Field::Flag::NormalTerm |
Field::Flag::Value Field::Flag::Value
}, },
@ -426,8 +404,7 @@ static constexpr std::array<Field, Field::id_size()>
"Priority", "Priority",
"prio:high", "prio:high",
'p', 'p',
Field::Flag::GMime | Field::Flag::BooleanTerm |
Field::Flag::NormalTerm |
Field::Flag::Value Field::Flag::Value
}, },
// Size // Size
@ -438,7 +415,6 @@ static constexpr std::array<Field, Field::id_size()>
"Message size in bytes", "Message size in bytes",
"size:1M..5M", "size:1M..5M",
'z', 'z',
Field::Flag::GMime |
Field::Flag::Value | Field::Flag::Value |
Field::Flag::Range Field::Flag::Range
}, },
@ -450,8 +426,7 @@ static constexpr std::array<Field, Field::id_size()>
"Mailing list (List-Id:)", "Mailing list (List-Id:)",
"list:mu-discuss.googlegroups.com", "list:mu-discuss.googlegroups.com",
'v', 'v',
Field::Flag::GMime | Field::Flag::BooleanTerm |
Field::Flag::NormalTerm |
Field::Flag::Value Field::Flag::Value
}, },
// ThreadId // ThreadId
@ -462,7 +437,8 @@ static constexpr std::array<Field, Field::id_size()>
"Thread a message belongs to", "Thread a message belongs to",
{}, {},
'w', 'w',
Field::Flag::NormalTerm Field::Flag::BooleanTerm |
Field::Flag::Value
}, },
}}; }};
@ -489,7 +465,7 @@ field_from_id(Field::Id id)
* @param func some callable * @param func some callable
*/ */
template <typename Func> template <typename Func>
void field_for_each(Func&& func) { constexpr void field_for_each(Func&& func) {
for (const auto& field: Fields) for (const auto& field: Fields)
func(field); func(field);
} }
@ -502,7 +478,7 @@ void field_for_each(Func&& func) {
* @return a message-field id, or nullopt if not found. * @return a message-field id, or nullopt if not found.
*/ */
template <typename Pred> template <typename Pred>
Option<Field> field_find_if(Pred&& pred) { constexpr Option<Field> field_find_if(Pred&& pred) {
for (auto&& field: Fields) for (auto&& field: Fields)
if (pred(field)) if (pred(field))
return field; return field;
@ -548,6 +524,5 @@ Option<Field> field_from_number(size_t id)
return field_from_id(static_cast<Field::Id>(id)); return field_from_id(static_cast<Field::Id>(id));
} }
} // namespace Mu } // namespace Mu
#endif /* MU_FIELDS_HH__ */ #endif /* MU_FIELDS_HH__ */

View File

@ -21,7 +21,6 @@
#define MU_FLAGS_HH__ #define MU_FLAGS_HH__
#include <algorithm> #include <algorithm>
#include <optional>
#include <string_view> #include <string_view>
#include <array> #include <array>
#include <utils/mu-utils.hh> #include <utils/mu-utils.hh>