[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