From: David Bremner <david@tethera.net>
To: notmuch@notmuchmail.org
Cc: David Bremner <david@tethera.net>
Subject: [PATCH 10/19] lib: factor out prefix related code to its own file
Date: Sat, 8 Aug 2020 11:16:44 -0300 [thread overview]
Message-ID: <20200808141653.1124111-11-david@tethera.net> (raw)
In-Reply-To: <20200808141653.1124111-1-david@tethera.net>
Reduce the size of database.cc, and limit the scope of prefix_table,
make sure it's accessed via a well-defined internal API.
---
lib/Makefile.local | 4 +-
lib/database-private.h | 7 ++
lib/database.cc | 204 ++-------------------------------------
lib/prefix.cc | 210 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 229 insertions(+), 196 deletions(-)
create mode 100644 lib/prefix.cc
diff --git a/lib/Makefile.local b/lib/Makefile.local
index b95ae216..15e71ef8 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -60,7 +60,9 @@ libnotmuch_cxx_srcs = \
$(dir)/regexp-fields.cc \
$(dir)/thread.cc \
$(dir)/thread-fp.cc \
- $(dir)/features.cc
+ $(dir)/features.cc \
+ $(dir)/prefix.cc
+
libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
diff --git a/lib/database-private.h b/lib/database-private.h
index 3b365c41..d83cf0d0 100644
--- a/lib/database-private.h
+++ b/lib/database-private.h
@@ -280,4 +280,11 @@ _notmuch_database_parse_features (const void *ctx, const char *features, unsigne
char *
_notmuch_database_print_features (const void *ctx, unsigned int features);
+/* prefix.cc */
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch);
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch);
+
#endif
diff --git a/lib/database.cc b/lib/database.cc
index ceba6dd5..0620de5a 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -263,80 +263,6 @@ _notmuch_database_mode (notmuch_database_t *notmuch)
* same thread.
*/
-/* With these prefix values we follow the conventions published here:
- *
- * https://xapian.org/docs/omega/termprefixes.html
- *
- * as much as makes sense. Note that I took some liberty in matching
- * the reserved prefix values to notmuch concepts, (for example, 'G'
- * is documented as "newsGroup (or similar entity - e.g. a web forum
- * name)", for which I think the thread is the closest analogue in
- * notmuch. This in spite of the fact that we will eventually be
- * storing mailing-list messages where 'G' for "mailing list name"
- * might be even a closer analogue. I'm treating the single-character
- * prefixes preferentially for core notmuch concepts (which will be
- * nearly universal to all mail messages).
- */
-
-static const
-prefix_t prefix_table[] = {
- /* name term prefix flags */
- { "type", "T", NOTMUCH_FIELD_NO_FLAGS },
- { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS },
- { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS },
- { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS },
- { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
- { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
- { "body", "", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "thread", "G", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "tag", "K", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "is", "K", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "id", "Q", NOTMUCH_FIELD_EXTERNAL },
- { "mid", "Q", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "path", "P", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
- /*
- * Unconditionally add ':' to reduce potential ambiguity with
- * overlapping prefixes and/or terms that start with capital
- * letters. See Xapian document termprefixes.html for related
- * discussion.
- */
- { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "date", NULL, NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "query", NULL, NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC |
- NOTMUCH_FIELD_PROCESSOR },
- { "to", "XTO", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC |
- NOTMUCH_FIELD_PROCESSOR },
-};
-
-static void
-_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
- if (prefix->prefix)
- notmuch->query_parser->add_prefix ("", prefix->prefix);
- if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
- notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
- else
- notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
-}
notmuch_string_map_iterator_t *
_notmuch_database_user_headers (notmuch_database_t *notmuch)
@@ -344,118 +270,6 @@ _notmuch_database_user_headers (notmuch_database_t *notmuch)
return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
}
-const char *
-_user_prefix (void *ctx, const char *name)
-{
- return talloc_asprintf (ctx, "XU%s:", name);
-}
-
-static notmuch_status_t
-_setup_user_query_fields (notmuch_database_t *notmuch)
-{
- notmuch_config_list_t *list;
- notmuch_status_t status;
-
- notmuch->user_prefix = _notmuch_string_map_create (notmuch);
- if (notmuch->user_prefix == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- notmuch->user_header = _notmuch_string_map_create (notmuch);
- if (notmuch->user_header == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
- if (status)
- return status;
-
- for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
-
- prefix_t query_field;
-
- const char *key = notmuch_config_list_key (list)
- + sizeof (CONFIG_HEADER_PREFIX) - 1;
-
- _notmuch_string_map_append (notmuch->user_prefix,
- key,
- _user_prefix (notmuch, key));
-
- _notmuch_string_map_append (notmuch->user_header,
- key,
- notmuch_config_list_value (list));
-
- query_field.name = talloc_strdup (notmuch, key);
- query_field.prefix = _user_prefix (notmuch, key);
- query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
- | NOTMUCH_FIELD_EXTERNAL;
-
- _setup_query_field_default (&query_field, notmuch);
- }
-
- notmuch_config_list_destroy (list);
-
- return NOTMUCH_STATUS_SUCCESS;
-}
-
-static void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
- if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
- Xapian::FieldProcessor *fp;
-
- if (STRNCMP_LITERAL (prefix->name, "date") == 0)
- fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
- else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
- fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
- else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
- fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
- else
- fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
- *notmuch->query_parser, notmuch))->release ();
-
- /* we treat all field-processor fields as boolean in order to get the raw input */
- if (prefix->prefix)
- notmuch->query_parser->add_prefix ("", prefix->prefix);
- notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
- } else {
- _setup_query_field_default (prefix, notmuch);
- }
-}
-
-const char *
-_find_prefix (const char *name)
-{
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- if (strcmp (name, prefix_table[i].name) == 0)
- return prefix_table[i].prefix;
- }
-
- INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
-
- return "";
-}
-
-/* Like find prefix, but include the possibility of user defined
- * prefixes specific to this database */
-
-const char *
-_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
-{
- unsigned int i;
-
- /*XXX TODO: reduce code duplication */
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- if (strcmp (name, prefix_table[i].name) == 0)
- return prefix_table[i].prefix;
- }
-
- if (notmuch->user_prefix)
- return _notmuch_string_map_get (notmuch->user_prefix, name);
-
- return NULL;
-}
-
const char *
notmuch_status_to_string (notmuch_status_t status)
{
@@ -825,7 +639,7 @@ notmuch_database_open_with_config (const char *database_path,
char *message = NULL;
struct stat st;
int err;
- unsigned int i, version;
+ unsigned version;
GKeyFile *key_file = NULL;
static int initialized = 0;
@@ -926,7 +740,7 @@ notmuch_database_open_with_config (const char *database_path,
/* Check features. */
incompat_features = NULL;
- notmuch->features = _parse_features (
+ notmuch->features = _notmuch_database_parse_features (
local, notmuch->xapian_db->get_metadata ("features").c_str (),
version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
&incompat_features);
@@ -980,13 +794,13 @@ notmuch_database_open_with_config (const char *database_path,
notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- const prefix_t *prefix = &prefix_table[i];
- if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
- _setup_query_field (prefix, notmuch);
- }
- }
- status = _setup_user_query_fields (notmuch);
+ status = _notmuch_database_setup_standard_query_fields (notmuch);
+ if (status)
+ goto DONE;
+
+ status = _notmuch_database_setup_user_query_fields (notmuch);
+ if (status)
+ goto DONE;
if (status == NOTMUCH_STATUS_SUCCESS)
status = _notmuch_config_load_from_database (notmuch);
diff --git a/lib/prefix.cc b/lib/prefix.cc
new file mode 100644
index 00000000..dd7b193d
--- /dev/null
+++ b/lib/prefix.cc
@@ -0,0 +1,210 @@
+#include "database-private.h"
+#include "query-fp.h"
+#include "thread-fp.h"
+#include "regexp-fields.h"
+#include "parse-time-vrp.h"
+
+typedef struct {
+ const char *name;
+ const char *prefix;
+ notmuch_field_flag_t flags;
+} prefix_t;
+
+/* With these prefix values we follow the conventions published here:
+ *
+ * https://xapian.org/docs/omega/termprefixes.html
+ *
+ * as much as makes sense. Note that I took some liberty in matching
+ * the reserved prefix values to notmuch concepts, (for example, 'G'
+ * is documented as "newsGroup (or similar entity - e.g. a web forum
+ * name)", for which I think the thread is the closest analogue in
+ * notmuch. This in spite of the fact that we will eventually be
+ * storing mailing-list messages where 'G' for "mailing list name"
+ * might be even a closer analogue. I'm treating the single-character
+ * prefixes preferentially for core notmuch concepts (which will be
+ * nearly universal to all mail messages).
+ */
+
+static const
+prefix_t prefix_table[] = {
+ /* name term prefix flags */
+ { "type", "T", NOTMUCH_FIELD_NO_FLAGS },
+ { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS },
+ { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS },
+ { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS },
+ { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
+ { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
+ { "body", "", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "thread", "G", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "tag", "K", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "is", "K", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "id", "Q", NOTMUCH_FIELD_EXTERNAL },
+ { "mid", "Q", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "path", "P", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
+ /*
+ * Unconditionally add ':' to reduce potential ambiguity with
+ * overlapping prefixes and/or terms that start with capital
+ * letters. See Xapian document termprefixes.html for related
+ * discussion.
+ */
+ { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "date", NULL, NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "query", NULL, NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "to", "XTO", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC |
+ NOTMUCH_FIELD_PROCESSOR },
+};
+
+static const char *
+_user_prefix (void *ctx, const char *name)
+{
+ return talloc_asprintf (ctx, "XU%s:", name);
+}
+
+const char *
+_find_prefix (const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+ if (strcmp (name, prefix_table[i].name) == 0)
+ return prefix_table[i].prefix;
+ }
+
+ INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
+
+ return "";
+}
+
+/* Like find prefix, but include the possibility of user defined
+ * prefixes specific to this database */
+
+const char *
+_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
+{
+ unsigned int i;
+
+ /*XXX TODO: reduce code duplication */
+ for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+ if (strcmp (name, prefix_table[i].name) == 0)
+ return prefix_table[i].prefix;
+ }
+
+ if (notmuch->user_prefix)
+ return _notmuch_string_map_get (notmuch->user_prefix, name);
+
+ return NULL;
+}
+
+static void
+_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+ if (prefix->prefix)
+ notmuch->query_parser->add_prefix ("", prefix->prefix);
+ if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
+ notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+ else
+ notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
+}
+
+static void
+_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+ if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
+ Xapian::FieldProcessor *fp;
+
+ if (STRNCMP_LITERAL (prefix->name, "date") == 0)
+ fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
+ else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
+ fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+ else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
+ fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+ else
+ fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
+ *notmuch->query_parser, notmuch))->release ();
+
+ /* we treat all field-processor fields as boolean in order to get the raw input */
+ if (prefix->prefix)
+ notmuch->query_parser->add_prefix ("", prefix->prefix);
+ notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
+ } else {
+ _setup_query_field_default (prefix, notmuch);
+ }
+}
+
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+ const prefix_t *prefix = &prefix_table[i];
+ if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
+ _setup_query_field (prefix, notmuch);
+ }
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch)
+{
+ notmuch_config_list_t *list;
+ notmuch_status_t status;
+
+ notmuch->user_prefix = _notmuch_string_map_create (notmuch);
+ if (notmuch->user_prefix == NULL)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ notmuch->user_header = _notmuch_string_map_create (notmuch);
+ if (notmuch->user_header == NULL)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
+ if (status)
+ return status;
+
+ for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+
+ prefix_t query_field;
+
+ const char *key = notmuch_config_list_key (list)
+ + sizeof (CONFIG_HEADER_PREFIX) - 1;
+
+ _notmuch_string_map_append (notmuch->user_prefix,
+ key,
+ _user_prefix (notmuch, key));
+
+ _notmuch_string_map_append (notmuch->user_header,
+ key,
+ notmuch_config_list_value (list));
+
+ query_field.name = talloc_strdup (notmuch, key);
+ query_field.prefix = _user_prefix (notmuch, key);
+ query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
+ | NOTMUCH_FIELD_EXTERNAL;
+
+ _setup_query_field_default (&query_field, notmuch);
+ }
+
+ notmuch_config_list_destroy (list);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
--
2.28.0
next prev parent reply other threads:[~2020-08-08 14:17 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-08-08 14:16 Initial implementation of merged config David Bremner
2020-08-08 14:16 ` [PATCH 01/19] test: use keys with group 'test' in T590-libconfig David Bremner
2020-08-08 14:16 ` [PATCH 02/19] lib: add stub for notmuch_database_open_with_config David Bremner
2020-08-09 7:19 ` Reto
2020-08-09 10:18 ` David Bremner
2020-08-08 14:16 ` [PATCH 03/19] lib: cache configuration information from database David Bremner
2020-08-08 14:16 ` [PATCH 04/19] WIP: add notmuch_config_get David Bremner
2020-08-08 14:16 ` [PATCH 05/19] WIP: add notmuch_config_set David Bremner
2020-08-08 14:16 ` [PATCH 06/19] WIP: initial retrieval of database path from config file David Bremner
2020-08-08 14:16 ` [PATCH 07/19] test/libconfig; use n_database_open_with_config David Bremner
2020-08-08 14:16 ` [PATCH 08/19] WIP: add _notmuch_config_load_from_file David Bremner
2020-08-08 14:16 ` [PATCH 09/19] lib: factor out feature name related code David Bremner
2020-08-08 14:16 ` David Bremner [this message]
2020-08-08 14:16 ` [PATCH 11/19] lib: factor out notmuch_database_open* related code to own file David Bremner
2020-08-08 14:16 ` [PATCH 12/19] WIP: adding fallbacks for NULL config_path David Bremner
2020-08-08 14:16 ` [PATCH 13/19] lib/config: delay setting talloc destructor David Bremner
2020-08-08 14:16 ` [PATCH 14/19] WIP: add strsplit_len David Bremner
2020-08-08 14:16 ` [PATCH 15/19] WIP: add config values iterator David Bremner
2020-08-08 14:16 ` [PATCH 16/19] test: add regression test for searching with alternate config David Bremner
2020-08-08 14:16 ` [PATCH 17/19] cli/config: add accessor for config file name David Bremner
2020-08-08 14:16 ` [PATCH 18/19] WIP converting notmuch search to new style config David Bremner
2020-08-08 14:16 ` [PATCH 19/19] WIP: switch notmuch-show to new config framework David Bremner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://notmuchmail.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200808141653.1124111-11-david@tethera.net \
--to=david@tethera.net \
--cc=notmuch@notmuchmail.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://yhetil.org/notmuch.git/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).