[PATCH] cli: add --output=filesandtags to notmuch search
Tomi Ollila
tomi.ollila at iki.fi
Sat Sep 8 06:46:41 PDT 2018
On Mon, Sep 03 2018, Vincent Breitmoser wrote:
> This commit adds a filesandtags output format, which outputs the
> filenames of all matching messages together with their tags. Files and
> tags are separated by newlines or null-bytes for --format=text or text0
> respectively, so that filenames and tags are on alternating lines. The
Do we already write output to multiple lines per message entry in text
output. If not this makes a change to this consistency. But we may
have spaces in filenames (Sent Items or such idiotism >;D) and in tags.
The only thing that comes mind as a separator would be // if filenams
and tags are put into same line, but that looks somewhat odd...
> json and sexp output formats are a list of maps, with a "filename" and
> "tags" key each.
>
> The rationale for this output parameter is to have a way of searching
> messages with notmuch in a scenario where display of message info is
> taken care of by another application based on filenames (e.g. mblaze),
> but that also want to make use of related tags. This use case isn't
> covered with any other notmuch search output format, and very cumbersome
> with notmuch show.
>
> It's possible to cover this workflow with a trivial python script.
> However in a quick test, a query that returned 40 messages was about
> three times slower for me with a python script with a hot cache, and
> even worse with a cold cache.
> ---
> NEWS | 7 ++
> doc/man1/notmuch-search.rst | 10 ++-
> notmuch-search.c | 58 +++++++++++-
> test/T090-search-output.sh | 171 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 241 insertions(+), 5 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 240d594b..18e8a08d 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,3 +1,10 @@
> +Command Line Interface
> +----------------------
> +
> +Add the --output=filesandtags option to `notmuch search`
> +
> + This option outputs both the filenames and tags of relevant messages.
> +
> Notmuch 0.27 (2018-06-13)
> =========================
>
> diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst
> index 654c5f2c..96593096 100644
> --- a/doc/man1/notmuch-search.rst
> +++ b/doc/man1/notmuch-search.rst
> @@ -35,7 +35,7 @@ Supported options for **search** include
> intended for programs that invoke **notmuch(1)** internally. If
> omitted, the latest supported version will be used.
>
> -``--output=(summary|threads|messages|files|tags)``
> +``--output=(summary|threads|messages|files|tags|filesandtags)``
> **summary**
> Output a summary of each thread with any message matching the
> search terms. The summary includes the thread ID, date, the
> @@ -71,6 +71,14 @@ Supported options for **search** include
> in other directories that are included in the output, although
> these files alone would not match the search.
>
> + **filesandtags**
> + Output the filenames of all messages matching the search terms, together
> + with their corresponding tags. Filenames and tags are output as lines in
> + an alternating fashion so that filenames are on odd lines and their tags
> + on the following even line (``--format=text``), as a JSON arrray of
> + objects (``--format=text``), or as an S-Expression list
> + (``--format=sexp``).
> +
> **tags**
> Output all tags that appear on any message matching the search
> terms, either one per line (``--format=text``), separated by null
> diff --git a/notmuch-search.c b/notmuch-search.c
> index 8f467db4..65167afa 100644
> --- a/notmuch-search.c
> +++ b/notmuch-search.c
> @@ -29,12 +29,13 @@ typedef enum {
> OUTPUT_MESSAGES = 1 << 2,
> OUTPUT_FILES = 1 << 3,
> OUTPUT_TAGS = 1 << 4,
> + OUTPUT_FILESANDTAGS = 1 << 5,
>
> /* Address command */
> - OUTPUT_SENDER = 1 << 5,
> - OUTPUT_RECIPIENTS = 1 << 6,
> - OUTPUT_COUNT = 1 << 7,
> - OUTPUT_ADDRESS = 1 << 8,
> + OUTPUT_SENDER = 1 << 6,
> + OUTPUT_RECIPIENTS = 1 << 7,
> + OUTPUT_COUNT = 1 << 8,
> + OUTPUT_ADDRESS = 1 << 9,
> } output_t;
>
> typedef enum {
> @@ -537,6 +538,7 @@ do_search_messages (search_context_t *ctx)
> notmuch_message_t *message;
> notmuch_messages_t *messages;
> notmuch_filenames_t *filenames;
> + notmuch_tags_t *tags;
> sprinter_t *format = ctx->format;
> int i;
> notmuch_status_t status;
> @@ -583,6 +585,52 @@ do_search_messages (search_context_t *ctx)
> }
>
> notmuch_filenames_destroy( filenames );
> + } else if (ctx->output == OUTPUT_FILESANDTAGS) {
> + int j;
> + filenames = notmuch_message_get_filenames (message);
> +
> + for (j = 1;
> + notmuch_filenames_valid (filenames);
> + notmuch_filenames_move_to_next (filenames), j++)
> + {
> +
> + if (ctx->dupe < 0 || ctx->dupe == j) {
> + format->begin_map (format);
> + format->map_key (format, "filename");
> +
> + format->string (format, notmuch_filenames_get (filenames));
> + if (format->is_text_printer) {
> + format->separator (format);
> + }
> +
> + format->map_key (format, "tags");
> + format->begin_list (format);
> +
> + bool first_tag = true;
> + for (tags = notmuch_message_get_tags (message);
> + notmuch_tags_valid (tags);
> + notmuch_tags_move_to_next (tags))
> + {
> + const char *tag = notmuch_tags_get (tags);
> + if (format->is_text_printer) {
> + if (first_tag)
> + first_tag = false;
> + else
> + fputc (' ', stdout);
> + fputs (tag, stdout);
> + } else { /* Structured Output */
> + format->string (format, tag);
> + }
> + }
> + notmuch_tags_destroy( tags );
> + format->end (format);
> +
> + format->end (format);
> + format->separator (format);
> + }
> +
> + }
> + notmuch_filenames_destroy( filenames );
>
> } else if (ctx->output == OUTPUT_MESSAGES) {
> /* special case 1 for speed */
> @@ -816,6 +864,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
> { "threads", OUTPUT_THREADS },
> { "messages", OUTPUT_MESSAGES },
> { "files", OUTPUT_FILES },
> + { "filesandtags", OUTPUT_FILESANDTAGS },
> { "tags", OUTPUT_TAGS },
> { 0, 0 } } },
> { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
> @@ -856,6 +905,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
> break;
> case OUTPUT_MESSAGES:
> case OUTPUT_FILES:
> + case OUTPUT_FILESANDTAGS:
> ret = do_search_messages (ctx);
> break;
> case OUTPUT_TAGS:
> diff --git a/test/T090-search-output.sh b/test/T090-search-output.sh
> index bf28d220..e294a5cc 100755
> --- a/test/T090-search-output.sh
> +++ b/test/T090-search-output.sh
> @@ -276,6 +276,177 @@ MAIL_DIR/new/04:2,
> EOF
> test_expect_equal_file EXPECTED OUTPUT
>
> +test_begin_subtest "--output=filesandtags"
> +notmuch search --output=filesandtags '*' | notmuch_search_files_sanitize >OUTPUT
> +cat <<EOF >EXPECTED
> +MAIL_DIR/cur/52:2,
> +inbox unread
> +MAIL_DIR/cur/53:2,
> +inbox unread
> +MAIL_DIR/cur/50:2,
> +inbox unread
> +MAIL_DIR/cur/49:2,
> +inbox unread
> +MAIL_DIR/cur/48:2,
> +inbox unread
> +MAIL_DIR/cur/47:2,
> +inbox unread
> +MAIL_DIR/cur/46:2,
> +inbox unread
> +MAIL_DIR/cur/45:2,
> +inbox unread
> +MAIL_DIR/cur/44:2,
> +inbox unread
> +MAIL_DIR/cur/43:2,
> +inbox unread
> +MAIL_DIR/cur/42:2,
> +inbox unread
> +MAIL_DIR/cur/41:2,
> +inbox unread
> +MAIL_DIR/cur/40:2,
> +inbox unread
> +MAIL_DIR/cur/39:2,
> +inbox unread
> +MAIL_DIR/cur/38:2,
> +inbox unread
> +MAIL_DIR/cur/37:2,
> +inbox unread
> +MAIL_DIR/cur/36:2,
> +inbox unread
> +MAIL_DIR/cur/35:2,
> +inbox unread
> +MAIL_DIR/cur/34:2,
> +inbox unread
> +MAIL_DIR/cur/33:2,
> +inbox unread
> +MAIL_DIR/cur/32:2,
> +inbox unread
> +MAIL_DIR/cur/31:2,
> +inbox unread
> +MAIL_DIR/cur/30:2,
> +inbox unread
> +MAIL_DIR/cur/29:2,
> +inbox unread
> +MAIL_DIR/bar/baz/new/28:2,
> +inbox unread
> +MAIL_DIR/bar/baz/new/27:2,
> +inbox unread
> +MAIL_DIR/bar/baz/cur/26:2,
> +inbox unread
> +MAIL_DIR/bar/baz/cur/25:2,
> +inbox unread
> +MAIL_DIR/bar/baz/24:2,
> +attachment inbox signed unread
> +MAIL_DIR/bar/baz/23:2,
> +attachment inbox signed unread
> +MAIL_DIR/bar/new/22:2,
> +inbox signed unread
> +MAIL_DIR/bar/new/21:2,
> +attachment inbox unread
> +MAIL_DIR/bar/cur/19:2,
> +inbox unread
> +MAIL_DIR/cur/51:2,
> +inbox unread
> +MAIL_DIR/bar/18:2,
> +inbox unread
> +MAIL_DIR/bar/cur/20:2,
> +inbox signed unread
> +MAIL_DIR/bar/17:2,
> +inbox unread
> +MAIL_DIR/foo/baz/new/16:2,
> +inbox unread
> +MAIL_DIR/foo/baz/new/15:2,
> +inbox unread
> +MAIL_DIR/foo/baz/cur/14:2,
> +inbox unread
> +MAIL_DIR/foo/baz/cur/13:2,
> +inbox unread
> +MAIL_DIR/foo/baz/12:2,
> +inbox unread
> +MAIL_DIR/foo/baz/11:2,
> +inbox unread
> +MAIL_DIR/foo/new/10:2,
> +inbox unread
> +MAIL_DIR/foo/new/09:2,
> +inbox unread
> +MAIL_DIR/foo/cur/08:2,
> +inbox signed unread
> +MAIL_DIR/foo/06:2,
> +inbox unread
> +MAIL_DIR/bar/baz/05:2,
> +attachment inbox unread
> +MAIL_DIR/new/04:2,
> +inbox signed unread
> +MAIL_DIR/foo/new/03:2,
> +inbox signed unread
> +MAIL_DIR/foo/cur/07:2,
> +inbox unread
> +MAIL_DIR/02:2,
> +inbox unread
> +MAIL_DIR/01:2,
> +inbox unread
> +EOF
> +test_expect_equal_file EXPECTED OUTPUT
> +
> +test_begin_subtest "--output=filesandtags --format=json"
> +notmuch search --output=filesandtags --format=json '*' | notmuch_search_files_sanitize >OUTPUT
> +cat <<EOF >EXPECTED
> +[{"filename": "MAIL_DIR/cur/52:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/53:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/50:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/49:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/48:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/47:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/46:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/45:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/44:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/43:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/42:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/41:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/40:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/39:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/38:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/37:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/36:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/35:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/34:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/33:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/32:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/31:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/30:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/29:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/new/28:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/new/27:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/cur/26:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/cur/25:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/24:2,", "tags": ["attachment", "inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/23:2,", "tags": ["attachment", "inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/bar/new/22:2,", "tags": ["inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/bar/new/21:2,", "tags": ["attachment", "inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/cur/19:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/cur/51:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/18:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/cur/20:2,", "tags": ["inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/bar/17:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/baz/new/16:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/baz/new/15:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/baz/cur/14:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/baz/cur/13:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/baz/12:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/baz/11:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/new/10:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/new/09:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/foo/cur/08:2,", "tags": ["inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/foo/06:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/bar/baz/05:2,", "tags": ["attachment", "inbox", "unread"]},
> +{"filename": "MAIL_DIR/new/04:2,", "tags": ["inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/foo/new/03:2,", "tags": ["inbox", "signed", "unread"]},
> +{"filename": "MAIL_DIR/foo/cur/07:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/02:2,", "tags": ["inbox", "unread"]},
> +{"filename": "MAIL_DIR/01:2,", "tags": ["inbox", "unread"]}]
> +EOF
> +test_expect_equal_file EXPECTED OUTPUT
> +
> dup1=$(notmuch search --output=files id:20091117232137.GA7669 at griffis1.net | head -n 1 | sed -e "s,$MAIL_DIR,MAIL_DIR,")
> dup2=$(notmuch search --output=files id:20091117232137.GA7669 at griffis1.net | tail -n 1 | sed -e "s,$MAIL_DIR,MAIL_DIR,")
>
> --
> 2.18.0
>
> _______________________________________________
> notmuch mailing list
> notmuch at notmuchmail.org
> https://notmuchmail.org/mailman/listinfo/notmuch
More information about the notmuch
mailing list