unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH 00/10] "notmuch address" command
@ 2014-11-02  0:49 Michal Sojka
  2014-11-02  0:49 ` [PATCH 01/10] cli: search: Rename options to context Michal Sojka
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

Hi all,

this patch series is continuation of address output patches [1] and
applies on current master. It is based on Jani's idea to have new
command "notmuch address", but share the implementation with search
[2].

I tried to do it in a bit more cleaner way than Jani's RFC. In
particular I wanted each command to process command line independently
and only share search functions.

Don't be scared by the number of patches. Most of them are trivial
refactoring. Patches 1-4 refactor the code so that it is easier to
split. Patch 5 is Jani's hierarchical command line parsing patch.
Patch 6 splits search functionality to new address command. Patch 7 is
minor refactoring. Patches 8-10 correspond to patches 5-7 in [1].

All patches pass the test suite.

Regards,
-Michal

[1] id:1414792441-29555-1-git-send-email-sojkam1@fel.cvut.cz
[2] id:cover.1414839970.git.jani@nikula.org


Jani Nikula (1):
  cli: add support for hierarchical command line option arrays

Michal Sojka (9):
  cli: search: Rename options to context
  cli: search: Move more variables into search_context_t
  cli: search: Convert ctx. to ctx->
  cli: search: Split notmuch_search_command to smaller functions
  cli: Introduce "notmuch address" command
  cli: search: Convert --output to keyword argument
  cli: address: Do not output duplicate addresses
  cli: address: Add --output=count
  cli: address: Add --filter-by option to configure address filtering

 command-line-arguments.c           |  14 +-
 command-line-arguments.h           |   1 +
 completion/notmuch-completion.bash |  48 +++-
 completion/notmuch-completion.zsh  |  11 +-
 doc/man1/notmuch-address.rst       | 140 ++++++++++++
 doc/man1/notmuch-search.rst        |  21 +-
 doc/man1/notmuch.rst               |   7 +-
 notmuch-client.h                   |   3 +
 notmuch-search.c                   | 440 ++++++++++++++++++++++++++-----------
 notmuch.c                          |   2 +
 test/T095-address.sh               | 148 +++++++++++++
 test/T097-address-filter-by.sh     |  73 ++++++
 12 files changed, 743 insertions(+), 165 deletions(-)
 create mode 100644 doc/man1/notmuch-address.rst
 create mode 100755 test/T095-address.sh
 create mode 100755 test/T097-address-filter-by.sh

-- 
2.1.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 01/10] cli: search: Rename options to context
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 02/10] cli: search: Move more variables into search_context_t Michal Sojka
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

Just text replacement, no other changes.
---
 notmuch-search.c | 136 +++++++++++++++++++++++++++----------------------------
 1 file changed, 68 insertions(+), 68 deletions(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index 671fe41..c354ddc 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -42,7 +42,7 @@ typedef struct {
     int offset;
     int limit;
     int dupe;
-} search_options_t;
+} search_context_t;
 
 typedef struct {
     const char *name;
@@ -89,39 +89,39 @@ get_thread_query (notmuch_thread_t *thread,
 }
 
 static int
-do_search_threads (search_options_t *opt)
+do_search_threads (search_context_t *ctx)
 {
     notmuch_thread_t *thread;
     notmuch_threads_t *threads;
     notmuch_tags_t *tags;
-    sprinter_t *format = opt->format;
+    sprinter_t *format = ctx->format;
     time_t date;
     int i;
 
-    if (opt->offset < 0) {
-	opt->offset += notmuch_query_count_threads (opt->query);
-	if (opt->offset < 0)
-	    opt->offset = 0;
+    if (ctx->offset < 0) {
+	ctx->offset += notmuch_query_count_threads (ctx->query);
+	if (ctx->offset < 0)
+	    ctx->offset = 0;
     }
 
-    threads = notmuch_query_search_threads (opt->query);
+    threads = notmuch_query_search_threads (ctx->query);
     if (threads == NULL)
 	return 1;
 
     format->begin_list (format);
 
     for (i = 0;
-	 notmuch_threads_valid (threads) && (opt->limit < 0 || i < opt->offset + opt->limit);
+	 notmuch_threads_valid (threads) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
 	 notmuch_threads_move_to_next (threads), i++)
     {
 	thread = notmuch_threads_get (threads);
 
-	if (i < opt->offset) {
+	if (i < ctx->offset) {
 	    notmuch_thread_destroy (thread);
 	    continue;
 	}
 
-	if (opt->output == OUTPUT_THREADS) {
+	if (ctx->output == OUTPUT_THREADS) {
 	    format->set_prefix (format, "thread");
 	    format->string (format,
 			    notmuch_thread_get_thread_id (thread));
@@ -138,7 +138,7 @@ do_search_threads (search_options_t *opt)
 
 	    format->begin_map (format);
 
-	    if (opt->sort == NOTMUCH_SORT_OLDEST_FIRST)
+	    if (ctx->sort == NOTMUCH_SORT_OLDEST_FIRST)
 		date = notmuch_thread_get_oldest_date (thread);
 	    else
 		date = notmuch_thread_get_newest_date (thread);
@@ -230,11 +230,11 @@ do_search_threads (search_options_t *opt)
 }
 
 static void
-print_mailbox (const search_options_t *opt, const mailbox_t *mailbox)
+print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 {
     const char *name = mailbox->name;
     const char *addr = mailbox->addr;
-    sprinter_t *format = opt->format;
+    sprinter_t *format = ctx->format;
     InternetAddress *ia = internet_address_mailbox_new (name, addr);
     char *name_addr;
 
@@ -263,7 +263,7 @@ print_mailbox (const search_options_t *opt, const mailbox_t *mailbox)
 
 /* Print addresses from InternetAddressList.  */
 static void
-process_address_list (const search_options_t *opt, InternetAddressList *list)
+process_address_list (const search_context_t *ctx, InternetAddressList *list)
 {
     InternetAddress *address;
     int i;
@@ -279,7 +279,7 @@ process_address_list (const search_options_t *opt, InternetAddressList *list)
 	    if (group_list == NULL)
 		continue;
 
-	    process_address_list (opt, group_list);
+	    process_address_list (ctx, group_list);
 	} else {
 	    InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
 	    mailbox_t mbx = {
@@ -287,14 +287,14 @@ process_address_list (const search_options_t *opt, InternetAddressList *list)
 		.addr = internet_address_mailbox_get_addr (mailbox),
 	    };
 
-	    print_mailbox (opt, &mbx);
+	    print_mailbox (ctx, &mbx);
 	}
     }
 }
 
 /* Print addresses from a message header.  */
 static void
-process_address_header (const search_options_t *opt, const char *value)
+process_address_header (const search_context_t *ctx, const char *value)
 {
     InternetAddressList *list;
 
@@ -305,42 +305,42 @@ process_address_header (const search_options_t *opt, const char *value)
     if (list == NULL)
 	return;
 
-    process_address_list (opt, list);
+    process_address_list (ctx, list);
 
     g_object_unref (list);
 }
 
 static int
-do_search_messages (search_options_t *opt)
+do_search_messages (search_context_t *ctx)
 {
     notmuch_message_t *message;
     notmuch_messages_t *messages;
     notmuch_filenames_t *filenames;
-    sprinter_t *format = opt->format;
+    sprinter_t *format = ctx->format;
     int i;
 
-    if (opt->offset < 0) {
-	opt->offset += notmuch_query_count_messages (opt->query);
-	if (opt->offset < 0)
-	    opt->offset = 0;
+    if (ctx->offset < 0) {
+	ctx->offset += notmuch_query_count_messages (ctx->query);
+	if (ctx->offset < 0)
+	    ctx->offset = 0;
     }
 
-    messages = notmuch_query_search_messages (opt->query);
+    messages = notmuch_query_search_messages (ctx->query);
     if (messages == NULL)
 	return 1;
 
     format->begin_list (format);
 
     for (i = 0;
-	 notmuch_messages_valid (messages) && (opt->limit < 0 || i < opt->offset + opt->limit);
+	 notmuch_messages_valid (messages) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
 	 notmuch_messages_move_to_next (messages), i++)
     {
-	if (i < opt->offset)
+	if (i < ctx->offset)
 	    continue;
 
 	message = notmuch_messages_get (messages);
 
-	if (opt->output == OUTPUT_FILES) {
+	if (ctx->output == OUTPUT_FILES) {
 	    int j;
 	    filenames = notmuch_message_get_filenames (message);
 
@@ -348,7 +348,7 @@ do_search_messages (search_options_t *opt)
 		 notmuch_filenames_valid (filenames);
 		 notmuch_filenames_move_to_next (filenames), j++)
 	    {
-		if (opt->dupe < 0 || opt->dupe == j) {
+		if (ctx->dupe < 0 || ctx->dupe == j) {
 		    format->string (format, notmuch_filenames_get (filenames));
 		    format->separator (format);
 		}
@@ -356,27 +356,27 @@ do_search_messages (search_options_t *opt)
 	    
 	    notmuch_filenames_destroy( filenames );
 
-	} else if (opt->output == OUTPUT_MESSAGES) {
+	} else if (ctx->output == OUTPUT_MESSAGES) {
 	    format->set_prefix (format, "id");
 	    format->string (format,
 			    notmuch_message_get_message_id (message));
 	    format->separator (format);
 	} else {
-	    if (opt->output & OUTPUT_SENDER) {
+	    if (ctx->output & OUTPUT_SENDER) {
 		const char *addrs;
 
 		addrs = notmuch_message_get_header (message, "from");
-		process_address_header (opt, addrs);
+		process_address_header (ctx, addrs);
 	    }
 
-	    if (opt->output & OUTPUT_RECIPIENTS) {
+	    if (ctx->output & OUTPUT_RECIPIENTS) {
 		const char *hdrs[] = { "to", "cc", "bcc" };
 		const char *addrs;
 		size_t j;
 
 		for (j = 0; j < ARRAY_SIZE (hdrs); j++) {
 		    addrs = notmuch_message_get_header (message, hdrs[j]);
-		    process_address_header (opt, addrs);
+		    process_address_header (ctx, addrs);
 		}
 	    }
 	}
@@ -393,13 +393,13 @@ do_search_messages (search_options_t *opt)
 
 static int
 do_search_tags (notmuch_database_t *notmuch,
-		const search_options_t *opt)
+		const search_context_t *ctx)
 {
     notmuch_messages_t *messages = NULL;
     notmuch_tags_t *tags;
     const char *tag;
-    sprinter_t *format = opt->format;
-    notmuch_query_t *query = opt->query;
+    sprinter_t *format = ctx->format;
+    notmuch_query_t *query = ctx->query;
 
     /* should the following only special case if no excluded terms
      * specified? */
@@ -444,7 +444,7 @@ int
 notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 {
     notmuch_database_t *notmuch;
-    search_options_t opt = {
+    search_context_t ctx = {
 	.sort = NOTMUCH_SORT_NEWEST_FIRST,
 	.output = 0,
 	.offset = 0,
@@ -464,7 +464,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     } format_sel = NOTMUCH_FORMAT_TEXT;
 
     notmuch_opt_desc_t options[] = {
-	{ NOTMUCH_OPT_KEYWORD, &opt.sort, "sort", 's',
+	{ NOTMUCH_OPT_KEYWORD, &ctx.sort, "sort", 's',
 	  (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
 				  { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
 				  { 0, 0 } } },
@@ -475,7 +475,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 				  { "text0", NOTMUCH_FORMAT_TEXT0 },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
-	{ NOTMUCH_OPT_KEYWORD_FLAGS, &opt.output, "output", 'o',
+	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx.output, "output", 'o',
 	  (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
 				  { "threads", OUTPUT_THREADS },
 				  { "messages", OUTPUT_MESSAGES },
@@ -490,9 +490,9 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
                                   { "flag", NOTMUCH_EXCLUDE_FLAG },
                                   { "all", NOTMUCH_EXCLUDE_ALL },
                                   { 0, 0 } } },
-	{ NOTMUCH_OPT_INT, &opt.offset, "offset", 'O', 0 },
-	{ NOTMUCH_OPT_INT, &opt.limit, "limit", 'L', 0  },
-	{ NOTMUCH_OPT_INT, &opt.dupe, "duplicate", 'D', 0  },
+	{ NOTMUCH_OPT_INT, &ctx.offset, "offset", 'O', 0 },
+	{ NOTMUCH_OPT_INT, &ctx.limit, "limit", 'L', 0  },
+	{ NOTMUCH_OPT_INT, &ctx.dupe, "duplicate", 'D', 0  },
 	{ 0, 0, 0, 0, 0 }
     };
 
@@ -500,25 +500,25 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     if (opt_index < 0)
 	return EXIT_FAILURE;
 
-    if (! opt.output)
-	opt.output = OUTPUT_SUMMARY;
+    if (! ctx.output)
+	ctx.output = OUTPUT_SUMMARY;
 
     switch (format_sel) {
     case NOTMUCH_FORMAT_TEXT:
-	opt.format = sprinter_text_create (config, stdout);
+	ctx.format = sprinter_text_create (config, stdout);
 	break;
     case NOTMUCH_FORMAT_TEXT0:
-	if (opt.output == OUTPUT_SUMMARY) {
+	if (ctx.output == OUTPUT_SUMMARY) {
 	    fprintf (stderr, "Error: --format=text0 is not compatible with --output=summary.\n");
 	    return EXIT_FAILURE;
 	}
-	opt.format = sprinter_text0_create (config, stdout);
+	ctx.format = sprinter_text0_create (config, stdout);
 	break;
     case NOTMUCH_FORMAT_JSON:
-	opt.format = sprinter_json_create (config, stdout);
+	ctx.format = sprinter_json_create (config, stdout);
 	break;
     case NOTMUCH_FORMAT_SEXP:
-	opt.format = sprinter_sexp_create (config, stdout);
+	ctx.format = sprinter_sexp_create (config, stdout);
 	break;
     default:
 	/* this should never happen */
@@ -541,15 +541,15 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	return EXIT_FAILURE;
     }
 
-    opt.query = notmuch_query_create (notmuch, query_str);
-    if (opt.query == NULL) {
+    ctx.query = notmuch_query_create (notmuch, query_str);
+    if (ctx.query == NULL) {
 	fprintf (stderr, "Out of memory\n");
 	return EXIT_FAILURE;
     }
 
-    notmuch_query_set_sort (opt.query, opt.sort);
+    notmuch_query_set_sort (ctx.query, ctx.sort);
 
-    if (exclude == NOTMUCH_EXCLUDE_FLAG && opt.output != OUTPUT_SUMMARY) {
+    if (exclude == NOTMUCH_EXCLUDE_FLAG && ctx.output != OUTPUT_SUMMARY) {
 	/* If we are not doing summary output there is nowhere to
 	 * print the excluded flag so fall back on including the
 	 * excluded messages. */
@@ -564,28 +564,28 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	search_exclude_tags = notmuch_config_get_search_exclude_tags
 	    (config, &search_exclude_tags_length);
 	for (i = 0; i < search_exclude_tags_length; i++)
-	    notmuch_query_add_tag_exclude (opt.query, search_exclude_tags[i]);
-	notmuch_query_set_omit_excluded (opt.query, exclude);
+	    notmuch_query_add_tag_exclude (ctx.query, search_exclude_tags[i]);
+	notmuch_query_set_omit_excluded (ctx.query, exclude);
     }
 
-    if (opt.output == OUTPUT_SUMMARY ||
-	opt.output == OUTPUT_THREADS)
-	ret = do_search_threads (&opt);
-    else if (opt.output == OUTPUT_MESSAGES ||
-	     opt.output == OUTPUT_FILES ||
-	     (opt.output & OUTPUT_ADDRESS_FLAGS && !(opt.output & ~OUTPUT_ADDRESS_FLAGS)))
-	ret = do_search_messages (&opt);
-    else if (opt.output == OUTPUT_TAGS)
-	ret = do_search_tags (notmuch, &opt);
+    if (ctx.output == OUTPUT_SUMMARY ||
+	ctx.output == OUTPUT_THREADS)
+	ret = do_search_threads (&ctx);
+    else if (ctx.output == OUTPUT_MESSAGES ||
+	     ctx.output == OUTPUT_FILES ||
+	     (ctx.output & OUTPUT_ADDRESS_FLAGS && !(ctx.output & ~OUTPUT_ADDRESS_FLAGS)))
+	ret = do_search_messages (&ctx);
+    else if (ctx.output == OUTPUT_TAGS)
+	ret = do_search_tags (notmuch, &ctx);
     else {
 	fprintf (stderr, "Error: the combination of outputs is not supported.\n");
 	ret = 1;
     }
 
-    notmuch_query_destroy (opt.query);
+    notmuch_query_destroy (ctx.query);
     notmuch_database_destroy (notmuch);
 
-    talloc_free (opt.format);
+    talloc_free (ctx.format);
 
     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
 }
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 02/10] cli: search: Move more variables into search_context_t
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
  2014-11-02  0:49 ` [PATCH 01/10] cli: search: Rename options to context Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 03/10] cli: search: Convert ctx. to ctx-> Michal Sojka
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

Just refactoring, no functional changes.
---
 notmuch-search.c | 49 ++++++++++++++++++++++++++-----------------------
 1 file changed, 26 insertions(+), 23 deletions(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index c354ddc..70e7d1b 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -34,8 +34,18 @@ typedef enum {
 
 #define OUTPUT_ADDRESS_FLAGS (OUTPUT_SENDER | OUTPUT_RECIPIENTS)
 
+typedef enum {
+    NOTMUCH_FORMAT_JSON,
+    NOTMUCH_FORMAT_TEXT,
+    NOTMUCH_FORMAT_TEXT0,
+    NOTMUCH_FORMAT_SEXP
+} format_sel_t;
+
 typedef struct {
+    notmuch_database_t *notmuch;
+    format_sel_t format_sel;
     sprinter_t *format;
+    notmuch_exclude_t exclude;
     notmuch_query_t *query;
     notmuch_sort_t sort;
     output_t output;
@@ -392,14 +402,14 @@ do_search_messages (search_context_t *ctx)
 }
 
 static int
-do_search_tags (notmuch_database_t *notmuch,
-		const search_context_t *ctx)
+do_search_tags (const search_context_t *ctx)
 {
     notmuch_messages_t *messages = NULL;
     notmuch_tags_t *tags;
     const char *tag;
     sprinter_t *format = ctx->format;
     notmuch_query_t *query = ctx->query;
+    notmuch_database_t *notmuch = ctx->notmuch;
 
     /* should the following only special case if no excluded terms
      * specified? */
@@ -443,8 +453,9 @@ do_search_tags (notmuch_database_t *notmuch,
 int
 notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 {
-    notmuch_database_t *notmuch;
     search_context_t ctx = {
+	.format_sel = NOTMUCH_FORMAT_TEXT,
+	.exclude = NOTMUCH_EXCLUDE_TRUE,
 	.sort = NOTMUCH_SORT_NEWEST_FIRST,
 	.output = 0,
 	.offset = 0,
@@ -453,22 +464,14 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     };
     char *query_str;
     int opt_index, ret;
-    notmuch_exclude_t exclude = NOTMUCH_EXCLUDE_TRUE;
     unsigned int i;
 
-    enum {
-	NOTMUCH_FORMAT_JSON,
-	NOTMUCH_FORMAT_TEXT,
-	NOTMUCH_FORMAT_TEXT0,
-	NOTMUCH_FORMAT_SEXP
-    } format_sel = NOTMUCH_FORMAT_TEXT;
-
     notmuch_opt_desc_t options[] = {
 	{ NOTMUCH_OPT_KEYWORD, &ctx.sort, "sort", 's',
 	  (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
 				  { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
 				  { 0, 0 } } },
-	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
+	{ NOTMUCH_OPT_KEYWORD, &ctx.format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
 				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "text", NOTMUCH_FORMAT_TEXT },
@@ -484,7 +487,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 				  { "files", OUTPUT_FILES },
 				  { "tags", OUTPUT_TAGS },
 				  { 0, 0 } } },
-        { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x',
+        { NOTMUCH_OPT_KEYWORD, &ctx.exclude, "exclude", 'x',
           (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
                                   { "false", NOTMUCH_EXCLUDE_FALSE },
                                   { "flag", NOTMUCH_EXCLUDE_FLAG },
@@ -503,7 +506,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     if (! ctx.output)
 	ctx.output = OUTPUT_SUMMARY;
 
-    switch (format_sel) {
+    switch (ctx.format_sel) {
     case NOTMUCH_FORMAT_TEXT:
 	ctx.format = sprinter_text_create (config, stdout);
 	break;
@@ -528,10 +531,10 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_exit_if_unsupported_format ();
 
     if (notmuch_database_open (notmuch_config_get_database_path (config),
-			       NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+			       NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx.notmuch))
 	return EXIT_FAILURE;
 
-    query_str = query_string_from_args (notmuch, argc-opt_index, argv+opt_index);
+    query_str = query_string_from_args (ctx.notmuch, argc-opt_index, argv+opt_index);
     if (query_str == NULL) {
 	fprintf (stderr, "Out of memory.\n");
 	return EXIT_FAILURE;
@@ -541,7 +544,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	return EXIT_FAILURE;
     }
 
-    ctx.query = notmuch_query_create (notmuch, query_str);
+    ctx.query = notmuch_query_create (ctx.notmuch, query_str);
     if (ctx.query == NULL) {
 	fprintf (stderr, "Out of memory\n");
 	return EXIT_FAILURE;
@@ -549,15 +552,15 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_query_set_sort (ctx.query, ctx.sort);
 
-    if (exclude == NOTMUCH_EXCLUDE_FLAG && ctx.output != OUTPUT_SUMMARY) {
+    if (ctx.exclude == NOTMUCH_EXCLUDE_FLAG && ctx.output != OUTPUT_SUMMARY) {
 	/* If we are not doing summary output there is nowhere to
 	 * print the excluded flag so fall back on including the
 	 * excluded messages. */
 	fprintf (stderr, "Warning: this output format cannot flag excluded messages.\n");
-	exclude = NOTMUCH_EXCLUDE_FALSE;
+	ctx.exclude = NOTMUCH_EXCLUDE_FALSE;
     }
 
-    if (exclude != NOTMUCH_EXCLUDE_FALSE) {
+    if (ctx.exclude != NOTMUCH_EXCLUDE_FALSE) {
 	const char **search_exclude_tags;
 	size_t search_exclude_tags_length;
 
@@ -565,7 +568,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	    (config, &search_exclude_tags_length);
 	for (i = 0; i < search_exclude_tags_length; i++)
 	    notmuch_query_add_tag_exclude (ctx.query, search_exclude_tags[i]);
-	notmuch_query_set_omit_excluded (ctx.query, exclude);
+	notmuch_query_set_omit_excluded (ctx.query, ctx.exclude);
     }
 
     if (ctx.output == OUTPUT_SUMMARY ||
@@ -576,14 +579,14 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	     (ctx.output & OUTPUT_ADDRESS_FLAGS && !(ctx.output & ~OUTPUT_ADDRESS_FLAGS)))
 	ret = do_search_messages (&ctx);
     else if (ctx.output == OUTPUT_TAGS)
-	ret = do_search_tags (notmuch, &ctx);
+	ret = do_search_tags (&ctx);
     else {
 	fprintf (stderr, "Error: the combination of outputs is not supported.\n");
 	ret = 1;
     }
 
     notmuch_query_destroy (ctx.query);
-    notmuch_database_destroy (notmuch);
+    notmuch_database_destroy (ctx.notmuch);
 
     talloc_free (ctx.format);
 
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 03/10] cli: search: Convert ctx. to ctx->
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
  2014-11-02  0:49 ` [PATCH 01/10] cli: search: Rename options to context Michal Sojka
  2014-11-02  0:49 ` [PATCH 02/10] cli: search: Move more variables into search_context_t Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 04/10] cli: search: Split notmuch_search_command to smaller functions Michal Sojka
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

Mostly text replacement.
---
 notmuch-search.c | 77 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 39 insertions(+), 38 deletions(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index 70e7d1b..ad7a92a 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -453,7 +453,7 @@ do_search_tags (const search_context_t *ctx)
 int
 notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 {
-    search_context_t ctx = {
+    search_context_t search_context = {
 	.format_sel = NOTMUCH_FORMAT_TEXT,
 	.exclude = NOTMUCH_EXCLUDE_TRUE,
 	.sort = NOTMUCH_SORT_NEWEST_FIRST,
@@ -462,23 +462,24 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	.limit = -1, /* unlimited */
 	.dupe = -1,
     };
+    search_context_t *ctx = &search_context;
     char *query_str;
     int opt_index, ret;
     unsigned int i;
 
     notmuch_opt_desc_t options[] = {
-	{ NOTMUCH_OPT_KEYWORD, &ctx.sort, "sort", 's',
+	{ NOTMUCH_OPT_KEYWORD, &ctx->sort, "sort", 's',
 	  (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
 				  { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
 				  { 0, 0 } } },
-	{ NOTMUCH_OPT_KEYWORD, &ctx.format_sel, "format", 'f',
+	{ NOTMUCH_OPT_KEYWORD, &ctx->format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
 				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "text", NOTMUCH_FORMAT_TEXT },
 				  { "text0", NOTMUCH_FORMAT_TEXT0 },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
-	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx.output, "output", 'o',
+	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
 	  (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
 				  { "threads", OUTPUT_THREADS },
 				  { "messages", OUTPUT_MESSAGES },
@@ -487,15 +488,15 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 				  { "files", OUTPUT_FILES },
 				  { "tags", OUTPUT_TAGS },
 				  { 0, 0 } } },
-        { NOTMUCH_OPT_KEYWORD, &ctx.exclude, "exclude", 'x',
+        { NOTMUCH_OPT_KEYWORD, &ctx->exclude, "exclude", 'x',
           (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
                                   { "false", NOTMUCH_EXCLUDE_FALSE },
                                   { "flag", NOTMUCH_EXCLUDE_FLAG },
                                   { "all", NOTMUCH_EXCLUDE_ALL },
                                   { 0, 0 } } },
-	{ NOTMUCH_OPT_INT, &ctx.offset, "offset", 'O', 0 },
-	{ NOTMUCH_OPT_INT, &ctx.limit, "limit", 'L', 0  },
-	{ NOTMUCH_OPT_INT, &ctx.dupe, "duplicate", 'D', 0  },
+	{ NOTMUCH_OPT_INT, &ctx->offset, "offset", 'O', 0 },
+	{ NOTMUCH_OPT_INT, &ctx->limit, "limit", 'L', 0  },
+	{ NOTMUCH_OPT_INT, &ctx->dupe, "duplicate", 'D', 0  },
 	{ 0, 0, 0, 0, 0 }
     };
 
@@ -503,25 +504,25 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     if (opt_index < 0)
 	return EXIT_FAILURE;
 
-    if (! ctx.output)
-	ctx.output = OUTPUT_SUMMARY;
+    if (! ctx->output)
+	ctx->output = OUTPUT_SUMMARY;
 
-    switch (ctx.format_sel) {
+    switch (ctx->format_sel) {
     case NOTMUCH_FORMAT_TEXT:
-	ctx.format = sprinter_text_create (config, stdout);
+	ctx->format = sprinter_text_create (config, stdout);
 	break;
     case NOTMUCH_FORMAT_TEXT0:
-	if (ctx.output == OUTPUT_SUMMARY) {
+	if (ctx->output == OUTPUT_SUMMARY) {
 	    fprintf (stderr, "Error: --format=text0 is not compatible with --output=summary.\n");
 	    return EXIT_FAILURE;
 	}
-	ctx.format = sprinter_text0_create (config, stdout);
+	ctx->format = sprinter_text0_create (config, stdout);
 	break;
     case NOTMUCH_FORMAT_JSON:
-	ctx.format = sprinter_json_create (config, stdout);
+	ctx->format = sprinter_json_create (config, stdout);
 	break;
     case NOTMUCH_FORMAT_SEXP:
-	ctx.format = sprinter_sexp_create (config, stdout);
+	ctx->format = sprinter_sexp_create (config, stdout);
 	break;
     default:
 	/* this should never happen */
@@ -531,10 +532,10 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_exit_if_unsupported_format ();
 
     if (notmuch_database_open (notmuch_config_get_database_path (config),
-			       NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx.notmuch))
+			       NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx->notmuch))
 	return EXIT_FAILURE;
 
-    query_str = query_string_from_args (ctx.notmuch, argc-opt_index, argv+opt_index);
+    query_str = query_string_from_args (ctx->notmuch, argc-opt_index, argv+opt_index);
     if (query_str == NULL) {
 	fprintf (stderr, "Out of memory.\n");
 	return EXIT_FAILURE;
@@ -544,51 +545,51 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	return EXIT_FAILURE;
     }
 
-    ctx.query = notmuch_query_create (ctx.notmuch, query_str);
-    if (ctx.query == NULL) {
+    ctx->query = notmuch_query_create (ctx->notmuch, query_str);
+    if (ctx->query == NULL) {
 	fprintf (stderr, "Out of memory\n");
 	return EXIT_FAILURE;
     }
 
-    notmuch_query_set_sort (ctx.query, ctx.sort);
+    notmuch_query_set_sort (ctx->query, ctx->sort);
 
-    if (ctx.exclude == NOTMUCH_EXCLUDE_FLAG && ctx.output != OUTPUT_SUMMARY) {
+    if (ctx->exclude == NOTMUCH_EXCLUDE_FLAG && ctx->output != OUTPUT_SUMMARY) {
 	/* If we are not doing summary output there is nowhere to
 	 * print the excluded flag so fall back on including the
 	 * excluded messages. */
 	fprintf (stderr, "Warning: this output format cannot flag excluded messages.\n");
-	ctx.exclude = NOTMUCH_EXCLUDE_FALSE;
+	ctx->exclude = NOTMUCH_EXCLUDE_FALSE;
     }
 
-    if (ctx.exclude != NOTMUCH_EXCLUDE_FALSE) {
+    if (ctx->exclude != NOTMUCH_EXCLUDE_FALSE) {
 	const char **search_exclude_tags;
 	size_t search_exclude_tags_length;
 
 	search_exclude_tags = notmuch_config_get_search_exclude_tags
 	    (config, &search_exclude_tags_length);
 	for (i = 0; i < search_exclude_tags_length; i++)
-	    notmuch_query_add_tag_exclude (ctx.query, search_exclude_tags[i]);
-	notmuch_query_set_omit_excluded (ctx.query, ctx.exclude);
+	    notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]);
+	notmuch_query_set_omit_excluded (ctx->query, ctx->exclude);
     }
 
-    if (ctx.output == OUTPUT_SUMMARY ||
-	ctx.output == OUTPUT_THREADS)
-	ret = do_search_threads (&ctx);
-    else if (ctx.output == OUTPUT_MESSAGES ||
-	     ctx.output == OUTPUT_FILES ||
-	     (ctx.output & OUTPUT_ADDRESS_FLAGS && !(ctx.output & ~OUTPUT_ADDRESS_FLAGS)))
-	ret = do_search_messages (&ctx);
-    else if (ctx.output == OUTPUT_TAGS)
-	ret = do_search_tags (&ctx);
+    if (ctx->output == OUTPUT_SUMMARY ||
+	ctx->output == OUTPUT_THREADS)
+	ret = do_search_threads (ctx);
+    else if (ctx->output == OUTPUT_MESSAGES ||
+	     ctx->output == OUTPUT_FILES ||
+	     (ctx->output & OUTPUT_ADDRESS_FLAGS && !(ctx->output & ~OUTPUT_ADDRESS_FLAGS)))
+	ret = do_search_messages (ctx);
+    else if (ctx->output == OUTPUT_TAGS)
+	ret = do_search_tags (ctx);
     else {
 	fprintf (stderr, "Error: the combination of outputs is not supported.\n");
 	ret = 1;
     }
 
-    notmuch_query_destroy (ctx.query);
-    notmuch_database_destroy (ctx.notmuch);
+    notmuch_query_destroy (ctx->query);
+    notmuch_database_destroy (ctx->notmuch);
 
-    talloc_free (ctx.format);
+    talloc_free (ctx->format);
 
     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
 }
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 04/10] cli: search: Split notmuch_search_command to smaller functions
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (2 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 03/10] cli: search: Convert ctx. to ctx-> Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 05/10] cli: add support for hierarchical command line option arrays Michal Sojka
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

In the next commit, these functions will be used to share some
functionality between search and address commands.
---
 notmuch-search.c | 155 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 86 insertions(+), 69 deletions(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index ad7a92a..d7e64eb 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -450,6 +450,89 @@ do_search_tags (const search_context_t *ctx)
     return 0;
 }
 
+static int
+_notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int argc, char *argv[])
+{
+    char *query_str;
+    unsigned int i;
+
+    switch (ctx->format_sel) {
+    case NOTMUCH_FORMAT_TEXT:
+	ctx->format = sprinter_text_create (config, stdout);
+	break;
+    case NOTMUCH_FORMAT_TEXT0:
+	if (ctx->output == OUTPUT_SUMMARY) {
+	    fprintf (stderr, "Error: --format=text0 is not compatible with --output=summary.\n");
+	    return EXIT_FAILURE;
+	}
+	ctx->format = sprinter_text0_create (config, stdout);
+	break;
+    case NOTMUCH_FORMAT_JSON:
+	ctx->format = sprinter_json_create (config, stdout);
+	break;
+    case NOTMUCH_FORMAT_SEXP:
+	ctx->format = sprinter_sexp_create (config, stdout);
+	break;
+    default:
+	/* this should never happen */
+	INTERNAL_ERROR("no output format selected");
+    }
+
+    notmuch_exit_if_unsupported_format ();
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+			       NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx->notmuch))
+	return EXIT_FAILURE;
+
+    query_str = query_string_from_args (ctx->notmuch, argc, argv);
+    if (query_str == NULL) {
+	fprintf (stderr, "Out of memory.\n");
+	return EXIT_FAILURE;
+    }
+    if (*query_str == '\0') {
+	fprintf (stderr, "Error: notmuch search requires at least one search term.\n");
+	return EXIT_FAILURE;
+    }
+
+    ctx->query = notmuch_query_create (ctx->notmuch, query_str);
+    if (ctx->query == NULL) {
+	fprintf (stderr, "Out of memory\n");
+	return EXIT_FAILURE;
+    }
+
+    notmuch_query_set_sort (ctx->query, ctx->sort);
+
+    if (ctx->exclude == NOTMUCH_EXCLUDE_FLAG && ctx->output != OUTPUT_SUMMARY) {
+	/* If we are not doing summary output there is nowhere to
+	 * print the excluded flag so fall back on including the
+	 * excluded messages. */
+	fprintf (stderr, "Warning: this output format cannot flag excluded messages.\n");
+	ctx->exclude = NOTMUCH_EXCLUDE_FALSE;
+    }
+
+    if (ctx->exclude != NOTMUCH_EXCLUDE_FALSE) {
+	const char **search_exclude_tags;
+	size_t search_exclude_tags_length;
+
+	search_exclude_tags = notmuch_config_get_search_exclude_tags
+	    (config, &search_exclude_tags_length);
+	for (i = 0; i < search_exclude_tags_length; i++)
+	    notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]);
+	notmuch_query_set_omit_excluded (ctx->query, ctx->exclude);
+    }
+
+    return 0;
+}
+
+static void
+_notmuch_search_cleanup (search_context_t *ctx)
+{
+    notmuch_query_destroy (ctx->query);
+    notmuch_database_destroy (ctx->notmuch);
+
+    talloc_free (ctx->format);
+}
+
 int
 notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 {
@@ -463,9 +546,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	.dupe = -1,
     };
     search_context_t *ctx = &search_context;
-    char *query_str;
     int opt_index, ret;
-    unsigned int i;
 
     notmuch_opt_desc_t options[] = {
 	{ NOTMUCH_OPT_KEYWORD, &ctx->sort, "sort", 's',
@@ -507,71 +588,10 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     if (! ctx->output)
 	ctx->output = OUTPUT_SUMMARY;
 
-    switch (ctx->format_sel) {
-    case NOTMUCH_FORMAT_TEXT:
-	ctx->format = sprinter_text_create (config, stdout);
-	break;
-    case NOTMUCH_FORMAT_TEXT0:
-	if (ctx->output == OUTPUT_SUMMARY) {
-	    fprintf (stderr, "Error: --format=text0 is not compatible with --output=summary.\n");
-	    return EXIT_FAILURE;
-	}
-	ctx->format = sprinter_text0_create (config, stdout);
-	break;
-    case NOTMUCH_FORMAT_JSON:
-	ctx->format = sprinter_json_create (config, stdout);
-	break;
-    case NOTMUCH_FORMAT_SEXP:
-	ctx->format = sprinter_sexp_create (config, stdout);
-	break;
-    default:
-	/* this should never happen */
-	INTERNAL_ERROR("no output format selected");
-    }
-
-    notmuch_exit_if_unsupported_format ();
-
-    if (notmuch_database_open (notmuch_config_get_database_path (config),
-			       NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx->notmuch))
+    if (_notmuch_search_prepare (ctx, config,
+				 argc - opt_index, argv + opt_index))
 	return EXIT_FAILURE;
 
-    query_str = query_string_from_args (ctx->notmuch, argc-opt_index, argv+opt_index);
-    if (query_str == NULL) {
-	fprintf (stderr, "Out of memory.\n");
-	return EXIT_FAILURE;
-    }
-    if (*query_str == '\0') {
-	fprintf (stderr, "Error: notmuch search requires at least one search term.\n");
-	return EXIT_FAILURE;
-    }
-
-    ctx->query = notmuch_query_create (ctx->notmuch, query_str);
-    if (ctx->query == NULL) {
-	fprintf (stderr, "Out of memory\n");
-	return EXIT_FAILURE;
-    }
-
-    notmuch_query_set_sort (ctx->query, ctx->sort);
-
-    if (ctx->exclude == NOTMUCH_EXCLUDE_FLAG && ctx->output != OUTPUT_SUMMARY) {
-	/* If we are not doing summary output there is nowhere to
-	 * print the excluded flag so fall back on including the
-	 * excluded messages. */
-	fprintf (stderr, "Warning: this output format cannot flag excluded messages.\n");
-	ctx->exclude = NOTMUCH_EXCLUDE_FALSE;
-    }
-
-    if (ctx->exclude != NOTMUCH_EXCLUDE_FALSE) {
-	const char **search_exclude_tags;
-	size_t search_exclude_tags_length;
-
-	search_exclude_tags = notmuch_config_get_search_exclude_tags
-	    (config, &search_exclude_tags_length);
-	for (i = 0; i < search_exclude_tags_length; i++)
-	    notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]);
-	notmuch_query_set_omit_excluded (ctx->query, ctx->exclude);
-    }
-
     if (ctx->output == OUTPUT_SUMMARY ||
 	ctx->output == OUTPUT_THREADS)
 	ret = do_search_threads (ctx);
@@ -586,10 +606,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	ret = 1;
     }
 
-    notmuch_query_destroy (ctx->query);
-    notmuch_database_destroy (ctx->notmuch);
-
-    talloc_free (ctx->format);
+    _notmuch_search_cleanup (ctx);
 
     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
 }
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 05/10] cli: add support for hierarchical command line option arrays
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (3 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 04/10] cli: search: Split notmuch_search_command to smaller functions Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 06/10] cli: Introduce "notmuch address" command Michal Sojka
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

From: Jani Nikula <jani@nikula.org>

NOTMUCH_OPT_INHERIT expects a notmuch_opt_desc_t * pointer in
output_var.
---
 command-line-arguments.c | 14 ++++++++------
 command-line-arguments.h |  1 +
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/command-line-arguments.c b/command-line-arguments.c
index c6f7269..60a0138 100644
--- a/command-line-arguments.c
+++ b/command-line-arguments.c
@@ -122,16 +122,18 @@ parse_position_arg (const char *arg_str, int pos_arg_index,
  */
 
 notmuch_bool_t
-parse_option (const char *arg,
-	      const notmuch_opt_desc_t *options) {
-
-    assert(arg);
+parse_option (const char *_arg, const notmuch_opt_desc_t *options)
+{
+    assert(_arg);
     assert(options);
 
-    arg += 2;
-
+    const char *arg = _arg + 2; /* _arg starts with -- */
     const notmuch_opt_desc_t *try;
     for (try = options; try->opt_type != NOTMUCH_OPT_END; try++) {
+	if (try->opt_type == NOTMUCH_OPT_INHERIT &&
+	    parse_option (_arg, try->output_var))
+	    return TRUE;
+
 	if (! try->name)
 	    continue;
 
diff --git a/command-line-arguments.h b/command-line-arguments.h
index 6444129..309aaf2 100644
--- a/command-line-arguments.h
+++ b/command-line-arguments.h
@@ -5,6 +5,7 @@
 
 enum notmuch_opt_type {
     NOTMUCH_OPT_END = 0,
+    NOTMUCH_OPT_INHERIT,	/* another options table */
     NOTMUCH_OPT_BOOLEAN,	/* --verbose              */
     NOTMUCH_OPT_INT,		/* --frob=8               */
     NOTMUCH_OPT_KEYWORD,	/* --format=raw|json|text */
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 06/10] cli: Introduce "notmuch address" command
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (4 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 05/10] cli: add support for hierarchical command line option arrays Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 07/10] cli: search: Convert --output to keyword argument Michal Sojka
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

This moves address-related functionality from search command to the
new address command. The implementation shares almost all code and
some command line options.

Options --offset and --limit were intentionally not included in the
address command, because they refer to messages numbers, which users
do not see in the output. This could confuse users because, for
example, they could see more addresses in the output that what was
specified with --limit. This functionality can be correctly
reimplemented for addresses later.

This was inspired by a patch from Jani Nikula.
---
 completion/notmuch-completion.bash |  42 ++++++++++++++-
 completion/notmuch-completion.zsh  |  10 +++-
 doc/man1/notmuch-address.rst       |  99 ++++++++++++++++++++++++++++++++++++
 doc/man1/notmuch-search.rst        |  20 +-------
 doc/man1/notmuch.rst               |   7 +--
 notmuch-client.h                   |   3 ++
 notmuch-search.c                   | 101 +++++++++++++++++++++++++------------
 notmuch.c                          |   2 +
 8 files changed, 228 insertions(+), 56 deletions(-)
 create mode 100644 doc/man1/notmuch-address.rst

diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash
index cfbd389..94ea2d5 100644
--- a/completion/notmuch-completion.bash
+++ b/completion/notmuch-completion.bash
@@ -294,7 +294,7 @@ _notmuch_search()
 	    return
 	    ;;
 	--output)
-	    COMPREPLY=( $( compgen -W "summary threads messages files tags sender recipients" -- "${cur}" ) )
+	    COMPREPLY=( $( compgen -W "summary threads messages files tags" -- "${cur}" ) )
 	    return
 	    ;;
 	--sort)
@@ -320,6 +320,44 @@ _notmuch_search()
     esac
 }
 
+_notmuch_address()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+	--format)
+	    COMPREPLY=( $( compgen -W "json sexp text text0" -- "${cur}" ) )
+	    return
+	    ;;
+	--output)
+	    COMPREPLY=( $( compgen -W "sender recipients" -- "${cur}" ) )
+	    return
+	    ;;
+	--sort)
+	    COMPREPLY=( $( compgen -W "newest-first oldest-first" -- "${cur}" ) )
+	    return
+	    ;;
+	--exclude)
+	    COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
+	    return
+	    ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+	-*)
+	    local options="--format= --output= --sort= --exclude="
+	    compopt -o nospace
+	    COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+	    ;;
+	*)
+	    _notmuch_search_terms
+	    ;;
+    esac
+}
+
 _notmuch_show()
 {
     local cur prev words cword split
@@ -393,7 +431,7 @@ _notmuch_tag()
 
 _notmuch()
 {
-    local _notmuch_commands="compact config count dump help insert new reply restore search setup show tag"
+    local _notmuch_commands="compact config count dump help insert new reply restore search address setup show tag"
     local arg cur prev words cword split
 
     # require bash-completion with _init_completion
diff --git a/completion/notmuch-completion.zsh b/completion/notmuch-completion.zsh
index 3e52a00..c606b75 100644
--- a/completion/notmuch-completion.zsh
+++ b/completion/notmuch-completion.zsh
@@ -10,6 +10,7 @@ _notmuch_commands()
     'setup:interactively set up notmuch for first use'
     'new:find and import any new message to the database'
     'search:search for messages matching the search terms, display matching threads as results'
+    'address:get addresses from messages matching the given search terms'
     'reply:constructs a reply template for a set of messages'
     'show:show all messages matching the search terms'
     'tag:add or remove tags for all messages matching the search terms'
@@ -53,7 +54,14 @@ _notmuch_search()
     '--max-threads=[display only the first x threads from the search results]:number of threads to show: ' \
     '--first=[omit the first x threads from the search results]:number of threads to omit: ' \
     '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
-    '--output=[select what to output]:output:((summary threads messages files tags sender recipients))'
+    '--output=[select what to output]:output:((summary threads messages files tags))'
+}
+
+_notmuch_address()
+{
+  _arguments -s : \
+    '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
+    '--output=[select what to output]:output:((sender recipients))'
 }
 
 _notmuch()
diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst
new file mode 100644
index 0000000..8109f11
--- /dev/null
+++ b/doc/man1/notmuch-address.rst
@@ -0,0 +1,99 @@
+===============
+notmuch-address
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **address** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Search for messages matching the given search terms, and display the
+addresses from them.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<search-terms>.
+
+Supported options for **address** include
+
+    ``--format=``\ (**json**\ \|\ **sexp**\ \|\ **text**\ \|\ **text0**)
+        Presents the results in either JSON, S-Expressions, newline
+        character separated plain-text (default), or null character
+        separated plain-text (compatible with **xargs(1)** -0 option
+        where available).
+
+    ``--format-version=N``
+        Use the specified structured output format version. This is
+        intended for programs that invoke **notmuch(1)** internally. If
+        omitted, the latest supported version will be used.
+
+    ``--output=(sender|recipients)``
+
+        Controls which information appears in the output. This option
+	can be given multiple times to combine different outputs.
+	Omitting this option is equivalent to
+	--output=sender --output=recipients.
+
+	**sender**
+            Output all addresses from the *From* header.
+
+	    Note: Searching for **sender** should be much faster than
+	    searching for **recipients**, because sender addresses are
+	    cached directly in the database whereas other addresses
+	    need to be fetched from message files.
+
+	**recipients**
+            Output all addresses from the *To*, *Cc* and *Bcc*
+            headers.
+
+    ``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
+        This option can be used to present results in either
+        chronological order (**oldest-first**) or reverse chronological
+        order (**newest-first**).
+
+        By default, results will be displayed in reverse chronological
+        order, (that is, the newest results will be displayed first).
+
+    ``--exclude=(true|false|all|flag)``
+        A message is called "excluded" if it matches at least one tag in
+        search.tag\_exclude that does not appear explicitly in the
+        search terms. This option specifies whether to omit excluded
+        messages in the search process.
+
+        The default value, **true**, prevents excluded messages from
+        matching the search terms.
+
+        **all** additionally prevents excluded messages from appearing
+        in displayed results, in effect behaving as though the excluded
+        messages do not exist.
+
+        **false** allows excluded messages to match search terms and
+        appear in displayed results. Excluded messages are still marked
+        in the relevant outputs.
+
+        **flag** only has an effect when ``--output=summary``. The
+        output is almost identical to **false**, but the "match count"
+        is the number of matching non-excluded messages in the thread,
+        rather than the number of matching messages.
+
+EXIT STATUS
+===========
+
+This command supports the following special exit status codes
+
+``20``
+    The requested format version is too old.
+
+``21``
+    The requested format version is too new.
+
+SEE ALSO
+========
+
+**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
+**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
+**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
+**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**,
+***notmuch-search(1)**
diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst
index b6607c9..e919811 100644
--- a/doc/man1/notmuch-search.rst
+++ b/doc/man1/notmuch-search.rst
@@ -78,25 +78,8 @@ Supported options for **search** include
             by null characters (--format=text0), as a JSON array
             (--format=json), or as an S-Expression list (--format=sexp).
 
-	**sender**
-            Output all addresses from the *From* header that appear on
-            any message matching the search terms, either one per line
-            (--format=text), separated by null characters
-            (--format=text0), as a JSON array (--format=json), or as
-            an S-Expression list (--format=sexp).
-
-	    Note: Searching for **sender** should be much faster than
-	    searching for **recipients**, because sender addresses are
-	    cached directly in the database whereas other addresses
-	    need to be fetched from message files.
-
-	**recipients**
-            Like **sender** but for addresses from *To*, *Cc* and
-	    *Bcc* headers.
-
 	This option can be given multiple times to combine different
-	outputs. Currently, this is only supported for **sender** and
-	**recipients** outputs.
+	outputs.
 
     ``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
         This option can be used to present results in either
@@ -169,3 +152,4 @@ SEE ALSO
 **notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
 **notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
 **notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+***notmuch-address(1)**
diff --git a/doc/man1/notmuch.rst b/doc/man1/notmuch.rst
index 9710294..98590a4 100644
--- a/doc/man1/notmuch.rst
+++ b/doc/man1/notmuch.rst
@@ -88,8 +88,8 @@ Several of the notmuch commands accept search terms with a common
 syntax. See **notmuch-search-terms**\ (7) for more details on the
 supported syntax.
 
-The **search**, **show** and **count** commands are used to query the
-email database.
+The **search**, **show**, **address** and **count** commands are used
+to query the email database.
 
 The **reply** command is useful for preparing a template for an email
 reply.
@@ -128,7 +128,8 @@ SEE ALSO
 **notmuch-config(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,
 **notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
 **notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**,
+***notmuch-address(1)**
 
 The notmuch website: **http://notmuchmail.org**
 
diff --git a/notmuch-client.h b/notmuch-client.h
index e1efbe0..5e0d475 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -199,6 +199,9 @@ int
 notmuch_search_command (notmuch_config_t *config, int argc, char *argv[]);
 
 int
+notmuch_address_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
 notmuch_setup_command (notmuch_config_t *config, int argc, char *argv[]);
 
 int
diff --git a/notmuch-search.c b/notmuch-search.c
index d7e64eb..7a78ecd 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -23,17 +23,18 @@
 #include "string-util.h"
 
 typedef enum {
+    /* Search command */
     OUTPUT_SUMMARY	= 1 << 0,
     OUTPUT_THREADS	= 1 << 1,
     OUTPUT_MESSAGES	= 1 << 2,
     OUTPUT_FILES	= 1 << 3,
     OUTPUT_TAGS		= 1 << 4,
+
+    /* Address command */
     OUTPUT_SENDER	= 1 << 5,
     OUTPUT_RECIPIENTS	= 1 << 6,
 } output_t;
 
-#define OUTPUT_ADDRESS_FLAGS (OUTPUT_SENDER | OUTPUT_RECIPIENTS)
-
 typedef enum {
     NOTMUCH_FORMAT_JSON,
     NOTMUCH_FORMAT_TEXT,
@@ -533,51 +534,55 @@ _notmuch_search_cleanup (search_context_t *ctx)
     talloc_free (ctx->format);
 }
 
+static search_context_t search_context = {
+    .format_sel = NOTMUCH_FORMAT_TEXT,
+    .exclude = NOTMUCH_EXCLUDE_TRUE,
+    .sort = NOTMUCH_SORT_NEWEST_FIRST,
+    .output = 0,
+    .offset = 0,
+    .limit = -1, /* unlimited */
+    .dupe = -1,
+};
+
+static const notmuch_opt_desc_t common_options[] = {
+    { NOTMUCH_OPT_KEYWORD, &search_context.sort, "sort", 's',
+      (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
+			      { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
+			      { 0, 0 } } },
+    { NOTMUCH_OPT_KEYWORD, &search_context.format_sel, "format", 'f',
+      (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+			      { "sexp", NOTMUCH_FORMAT_SEXP },
+			      { "text", NOTMUCH_FORMAT_TEXT },
+			      { "text0", NOTMUCH_FORMAT_TEXT0 },
+			      { 0, 0 } } },
+    { NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
+    { NOTMUCH_OPT_KEYWORD, &search_context.exclude, "exclude", 'x',
+      (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
+			      { "false", NOTMUCH_EXCLUDE_FALSE },
+			      { "flag", NOTMUCH_EXCLUDE_FLAG },
+			      { "all", NOTMUCH_EXCLUDE_ALL },
+			      { 0, 0 } } },
+    { 0, 0, 0, 0, 0 }
+};
+
 int
 notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 {
-    search_context_t search_context = {
-	.format_sel = NOTMUCH_FORMAT_TEXT,
-	.exclude = NOTMUCH_EXCLUDE_TRUE,
-	.sort = NOTMUCH_SORT_NEWEST_FIRST,
-	.output = 0,
-	.offset = 0,
-	.limit = -1, /* unlimited */
-	.dupe = -1,
-    };
     search_context_t *ctx = &search_context;
     int opt_index, ret;
 
     notmuch_opt_desc_t options[] = {
-	{ NOTMUCH_OPT_KEYWORD, &ctx->sort, "sort", 's',
-	  (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
-				  { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
-				  { 0, 0 } } },
-	{ NOTMUCH_OPT_KEYWORD, &ctx->format_sel, "format", 'f',
-	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
-				  { "sexp", NOTMUCH_FORMAT_SEXP },
-				  { "text", NOTMUCH_FORMAT_TEXT },
-				  { "text0", NOTMUCH_FORMAT_TEXT0 },
-				  { 0, 0 } } },
-	{ NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },
 	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
 	  (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
 				  { "threads", OUTPUT_THREADS },
 				  { "messages", OUTPUT_MESSAGES },
-				  { "sender", OUTPUT_SENDER },
-				  { "recipients", OUTPUT_RECIPIENTS },
 				  { "files", OUTPUT_FILES },
 				  { "tags", OUTPUT_TAGS },
 				  { 0, 0 } } },
-        { NOTMUCH_OPT_KEYWORD, &ctx->exclude, "exclude", 'x',
-          (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
-                                  { "false", NOTMUCH_EXCLUDE_FALSE },
-                                  { "flag", NOTMUCH_EXCLUDE_FLAG },
-                                  { "all", NOTMUCH_EXCLUDE_ALL },
-                                  { 0, 0 } } },
 	{ NOTMUCH_OPT_INT, &ctx->offset, "offset", 'O', 0 },
 	{ NOTMUCH_OPT_INT, &ctx->limit, "limit", 'L', 0  },
 	{ NOTMUCH_OPT_INT, &ctx->dupe, "duplicate", 'D', 0  },
+	{ NOTMUCH_OPT_INHERIT, &common_options, NULL, 0, 0 },
 	{ 0, 0, 0, 0, 0 }
     };
 
@@ -596,8 +601,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	ctx->output == OUTPUT_THREADS)
 	ret = do_search_threads (ctx);
     else if (ctx->output == OUTPUT_MESSAGES ||
-	     ctx->output == OUTPUT_FILES ||
-	     (ctx->output & OUTPUT_ADDRESS_FLAGS && !(ctx->output & ~OUTPUT_ADDRESS_FLAGS)))
+	     ctx->output == OUTPUT_FILES)
 	ret = do_search_messages (ctx);
     else if (ctx->output == OUTPUT_TAGS)
 	ret = do_search_tags (ctx);
@@ -610,3 +614,36 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 
     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
 }
+
+int
+notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    search_context_t *ctx = &search_context;
+    int opt_index, ret;
+
+    notmuch_opt_desc_t options[] = {
+	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
+	  (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
+				  { "recipients", OUTPUT_RECIPIENTS },
+				  { 0, 0 } } },
+	{ NOTMUCH_OPT_INHERIT, &common_options, NULL, 0, 0 },
+	{ 0, 0, 0, 0, 0 }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+	return EXIT_FAILURE;
+
+    if (! ctx->output)
+	search_context.output = OUTPUT_SENDER | OUTPUT_RECIPIENTS;
+
+    if (_notmuch_search_prepare (ctx, config,
+				 argc - opt_index, argv + opt_index))
+	return EXIT_FAILURE;
+
+    ret = do_search_messages (ctx);
+
+    _notmuch_search_cleanup (ctx);
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch.c b/notmuch.c
index dcda039..0fac099 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -54,6 +54,8 @@ static command_t commands[] = {
       "Add a new message into the maildir and notmuch database." },
     { "search", notmuch_search_command, FALSE,
       "Search for messages matching the given search terms." },
+    { "address", notmuch_address_command, FALSE,
+      "Get addresses from messages matching the given search terms." },
     { "show", notmuch_show_command, FALSE,
       "Show all messages matching the search terms." },
     { "count", notmuch_count_command, FALSE,
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 07/10] cli: search: Convert --output to keyword argument
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (5 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 06/10] cli: Introduce "notmuch address" command Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 08/10] cli: address: Do not output duplicate addresses Michal Sojka
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

Now, when address related outputs are in a separate command, it makes
no sense to combine multiple --output options in search command line.
Using switch statement to handle different outputs is more readable
than a series of if statements.
---
 doc/man1/notmuch-search.rst |  3 ---
 notmuch-search.c            | 25 +++++++++++++------------
 2 files changed, 13 insertions(+), 15 deletions(-)

diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst
index e919811..e7b5ad4 100644
--- a/doc/man1/notmuch-search.rst
+++ b/doc/man1/notmuch-search.rst
@@ -78,9 +78,6 @@ Supported options for **search** include
             by null characters (--format=text0), as a JSON array
             (--format=json), or as an S-Expression list (--format=sexp).
 
-	This option can be given multiple times to combine different
-	outputs.
-
     ``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
         This option can be used to present results in either
         chronological order (**oldest-first**) or reverse chronological
diff --git a/notmuch-search.c b/notmuch-search.c
index 7a78ecd..4b9a372 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -572,7 +572,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     int opt_index, ret;
 
     notmuch_opt_desc_t options[] = {
-	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
+	{ NOTMUCH_OPT_KEYWORD, &ctx->output, "output", 'o',
 	  (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
 				  { "threads", OUTPUT_THREADS },
 				  { "messages", OUTPUT_MESSAGES },
@@ -586,28 +586,29 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
 	{ 0, 0, 0, 0, 0 }
     };
 
+    ctx->output = OUTPUT_SUMMARY;
     opt_index = parse_arguments (argc, argv, options, 1);
     if (opt_index < 0)
 	return EXIT_FAILURE;
 
-    if (! ctx->output)
-	ctx->output = OUTPUT_SUMMARY;
-
     if (_notmuch_search_prepare (ctx, config,
 				 argc - opt_index, argv + opt_index))
 	return EXIT_FAILURE;
 
-    if (ctx->output == OUTPUT_SUMMARY ||
-	ctx->output == OUTPUT_THREADS)
+    switch (ctx->output) {
+    case OUTPUT_SUMMARY:
+    case OUTPUT_THREADS:
 	ret = do_search_threads (ctx);
-    else if (ctx->output == OUTPUT_MESSAGES ||
-	     ctx->output == OUTPUT_FILES)
+	break;
+    case OUTPUT_MESSAGES:
+    case OUTPUT_FILES:
 	ret = do_search_messages (ctx);
-    else if (ctx->output == OUTPUT_TAGS)
+	break;
+    case OUTPUT_TAGS:
 	ret = do_search_tags (ctx);
-    else {
-	fprintf (stderr, "Error: the combination of outputs is not supported.\n");
-	ret = 1;
+	break;
+    default:
+	INTERNAL_ERROR ("Unexpected output");
     }
 
     _notmuch_search_cleanup (ctx);
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 08/10] cli: address: Do not output duplicate addresses
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (6 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 07/10] cli: search: Convert --output to keyword argument Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:49 ` [PATCH 09/10] cli: address: Add --output=count Michal Sojka
  2014-11-02  0:50 ` [PATCH 10/10] cli: address: Add --filter-by option to configure address filtering Michal Sojka
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

This filters out duplicate addresses from address command output.

It also also adds tests for the address command.

The code here is an extended version of a patch from Jani Nikula.
---
 doc/man1/notmuch-address.rst |   2 +-
 notmuch-search.c             |  40 ++++++++++++++++-
 test/T095-address.sh         | 100 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+), 2 deletions(-)
 create mode 100755 test/T095-address.sh

diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst
index 8109f11..96512b7 100644
--- a/doc/man1/notmuch-address.rst
+++ b/doc/man1/notmuch-address.rst
@@ -11,7 +11,7 @@ DESCRIPTION
 ===========
 
 Search for messages matching the given search terms, and display the
-addresses from them.
+addresses from them. Duplicate addresses are filtered out.
 
 See **notmuch-search-terms(7)** for details of the supported syntax for
 <search-terms>.
diff --git a/notmuch-search.c b/notmuch-search.c
index 4b9a372..c765017 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -53,6 +53,7 @@ typedef struct {
     int offset;
     int limit;
     int dupe;
+    GHashTable *addresses;
 } search_context_t;
 
 typedef struct {
@@ -240,6 +241,27 @@ do_search_threads (search_context_t *ctx)
     return 0;
 }
 
+/* Returns TRUE iff name and addr is duplicate. */
+static notmuch_bool_t
+is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
+{
+    notmuch_bool_t duplicate;
+    char *key;
+
+    key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
+    if (! key)
+	return FALSE;
+
+    duplicate = g_hash_table_lookup_extended (ctx->addresses, key, NULL, NULL);
+
+    if (! duplicate)
+	g_hash_table_insert (ctx->addresses, key, NULL);
+    else
+	talloc_free (key);
+
+    return duplicate;
+}
+
 static void
 print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 {
@@ -274,7 +296,8 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 
 /* Print addresses from InternetAddressList.  */
 static void
-process_address_list (const search_context_t *ctx, InternetAddressList *list)
+process_address_list (const search_context_t *ctx,
+		      InternetAddressList *list)
 {
     InternetAddress *address;
     int i;
@@ -298,6 +321,9 @@ process_address_list (const search_context_t *ctx, InternetAddressList *list)
 		.addr = internet_address_mailbox_get_addr (mailbox),
 	    };
 
+	    if (is_duplicate (ctx, mbx.name, mbx.addr))
+		continue;
+
 	    print_mailbox (ctx, &mbx);
 	}
     }
@@ -321,6 +347,12 @@ process_address_header (const search_context_t *ctx, const char *value)
     g_object_unref (list);
 }
 
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
 static int
 do_search_messages (search_context_t *ctx)
 {
@@ -642,8 +674,14 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
 				 argc - opt_index, argv + opt_index))
 	return EXIT_FAILURE;
 
+    ctx->addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
+					    _my_talloc_free_for_g_hash, NULL);
+
     ret = do_search_messages (ctx);
 
+    g_hash_table_unref (ctx->addresses);
+
+
     _notmuch_search_cleanup (ctx);
 
     return ret ? EXIT_FAILURE : EXIT_SUCCESS;
diff --git a/test/T095-address.sh b/test/T095-address.sh
new file mode 100755
index 0000000..8a256d2
--- /dev/null
+++ b/test/T095-address.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+test_description='"notmuch address" in several variants'
+. ./test-lib.sh
+
+add_email_corpus
+
+test_begin_subtest "--output=sender"
+notmuch address --output=sender '*' >OUTPUT
+cat <<EOF >EXPECTED
+François Boulogne <boulogne.f@gmail.com>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+Chris Wilson <chris@chris-wilson.co.uk>
+Carl Worth <cworth@cworth.org>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Keith Packard <keithp@keithp.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Jan Janak <jan@ryngle.com>
+Stewart Smith <stewart@flamingspork.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Aron Griffis <agriffis@n01se.net>
+Adrian Perez de Castro <aperez@igalia.com>
+Israel Herraiz <isra@herraiz.org>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=sender --format=json"
+notmuch address --output=sender --format=json '*' >OUTPUT
+cat <<EOF >EXPECTED
+[{"name": "François Boulogne", "address": "boulogne.f@gmail.com", "name-addr": "François Boulogne <boulogne.f@gmail.com>"},
+{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu", "name-addr": "Olivier Berger <olivier.berger@it-sudparis.eu>"},
+{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk", "name-addr": "Chris Wilson <chris@chris-wilson.co.uk>"},
+{"name": "Carl Worth", "address": "cworth@cworth.org", "name-addr": "Carl Worth <cworth@cworth.org>"},
+{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alexander Botero-Lowry <alex.boterolowry@gmail.com>"},
+{"name": "Keith Packard", "address": "keithp@keithp.com", "name-addr": "Keith Packard <keithp@keithp.com>"},
+{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com", "name-addr": "Jjgod Jiang <gzjjgod@gmail.com>"},
+{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com", "name-addr": "Rolland Santimano <rollandsantimano@yahoo.com>"},
+{"name": "Jan Janak", "address": "jan@ryngle.com", "name-addr": "Jan Janak <jan@ryngle.com>"},
+{"name": "Stewart Smith", "address": "stewart@flamingspork.com", "name-addr": "Stewart Smith <stewart@flamingspork.com>"},
+{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu", "name-addr": "Lars Kellogg-Stedman <lars@seas.harvard.edu>"},
+{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alex Botero-Lowry <alex.boterolowry@gmail.com>"},
+{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org", "name-addr": "Ingmar Vanhassel <ingmar@exherbo.org>"},
+{"name": "Aron Griffis", "address": "agriffis@n01se.net", "name-addr": "Aron Griffis <agriffis@n01se.net>"},
+{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com", "name-addr": "Adrian Perez de Castro <aperez@igalia.com>"},
+{"name": "Israel Herraiz", "address": "isra@herraiz.org", "name-addr": "Israel Herraiz <isra@herraiz.org>"},
+{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net", "name-addr": "Mikhail Gusarov <dottedmag@dottedmag.net>"}]
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients"
+notmuch address --output=recipients '*' >OUTPUT
+cat <<EOF >EXPECTED
+Allan McRae <allan@archlinux.org>
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+olivier.berger@it-sudparis.eu
+notmuch@notmuchmail.org
+notmuch <notmuch@notmuchmail.org>
+Keith Packard <keithp@keithp.com>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=sender --output=recipients"
+notmuch address --output=sender --output=recipients '*' >OUTPUT
+cat <<EOF >EXPECTED
+François Boulogne <boulogne.f@gmail.com>
+Allan McRae <allan@archlinux.org>
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+olivier.berger@it-sudparis.eu
+Chris Wilson <chris@chris-wilson.co.uk>
+notmuch@notmuchmail.org
+Carl Worth <cworth@cworth.org>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Keith Packard <keithp@keithp.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Jan Janak <jan@ryngle.com>
+Stewart Smith <stewart@flamingspork.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+notmuch <notmuch@notmuchmail.org>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Aron Griffis <agriffis@n01se.net>
+Adrian Perez de Castro <aperez@igalia.com>
+Israel Herraiz <isra@herraiz.org>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "No --output"
+notmuch address --output=sender --output=recipients '*' >OUTPUT
+# Use EXPECTED from previous subtest
+test_expect_equal_file OUTPUT EXPECTED
+
+
+test_done
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 09/10] cli: address: Add --output=count
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (7 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 08/10] cli: address: Do not output duplicate addresses Michal Sojka
@ 2014-11-02  0:49 ` Michal Sojka
  2014-11-02  0:50 ` [PATCH 10/10] cli: address: Add --filter-by option to configure address filtering Michal Sojka
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:49 UTC (permalink / raw)
  To: notmuch

This output prints how many times was each address encountered during
search.
---
 completion/notmuch-completion.bash |  2 +-
 completion/notmuch-completion.zsh  |  2 +-
 doc/man1/notmuch-address.rst       |  7 ++++++
 notmuch-search.c                   | 49 ++++++++++++++++++++++++++++++++------
 test/T095-address.sh               | 48 +++++++++++++++++++++++++++++++++++++
 5 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash
index 94ea2d5..db152f3 100644
--- a/completion/notmuch-completion.bash
+++ b/completion/notmuch-completion.bash
@@ -332,7 +332,7 @@ _notmuch_address()
 	    return
 	    ;;
 	--output)
-	    COMPREPLY=( $( compgen -W "sender recipients" -- "${cur}" ) )
+	    COMPREPLY=( $( compgen -W "sender recipients count" -- "${cur}" ) )
 	    return
 	    ;;
 	--sort)
diff --git a/completion/notmuch-completion.zsh b/completion/notmuch-completion.zsh
index c606b75..8968562 100644
--- a/completion/notmuch-completion.zsh
+++ b/completion/notmuch-completion.zsh
@@ -61,7 +61,7 @@ _notmuch_address()
 {
   _arguments -s : \
     '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
-    '--output=[select what to output]:output:((sender recipients))'
+    '--output=[select what to output]:output:((sender recipients count))'
 }
 
 _notmuch()
diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst
index 96512b7..18473a7 100644
--- a/doc/man1/notmuch-address.rst
+++ b/doc/man1/notmuch-address.rst
@@ -48,6 +48,13 @@ Supported options for **address** include
             Output all addresses from the *To*, *Cc* and *Bcc*
             headers.
 
+	**count**
+	    Print the count of how many times was the address
+	    encountered during search.
+
+	    Note: With this option, addresses are printed only after
+	    the whole search is finished. This may take long time.
+
     ``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
         This option can be used to present results in either
         chronological order (**oldest-first**) or reverse chronological
diff --git a/notmuch-search.c b/notmuch-search.c
index c765017..b45d480 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -33,6 +33,7 @@ typedef enum {
     /* Address command */
     OUTPUT_SENDER	= 1 << 5,
     OUTPUT_RECIPIENTS	= 1 << 6,
+    OUTPUT_COUNT	= 1 << 7,
 } output_t;
 
 typedef enum {
@@ -59,6 +60,7 @@ typedef struct {
 typedef struct {
     const char *name;
     const char *addr;
+    int count;
 } mailbox_t;
 
 /* Return two stable query strings that identify exactly the matched
@@ -247,17 +249,24 @@ is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
 {
     notmuch_bool_t duplicate;
     char *key;
+    mailbox_t *mailbox;
 
     key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
     if (! key)
 	return FALSE;
 
-    duplicate = g_hash_table_lookup_extended (ctx->addresses, key, NULL, NULL);
+    duplicate = g_hash_table_lookup_extended (ctx->addresses, key, NULL, (gpointer)&mailbox);
 
-    if (! duplicate)
-	g_hash_table_insert (ctx->addresses, key, NULL);
-    else
+    if (! duplicate) {
+	mailbox = talloc (ctx->format, mailbox_t);
+	mailbox->name = talloc_strdup (mailbox, name);
+	mailbox->addr = talloc_strdup (mailbox, addr);
+	mailbox->count = 1;
+	g_hash_table_insert (ctx->addresses, key, mailbox);
+    } else {
+	mailbox->count++;
 	talloc_free (key);
+    }
 
     return duplicate;
 }
@@ -267,6 +276,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 {
     const char *name = mailbox->name;
     const char *addr = mailbox->addr;
+    int count = mailbox->count;
     sprinter_t *format = ctx->format;
     InternetAddress *ia = internet_address_mailbox_new (name, addr);
     char *name_addr;
@@ -276,6 +286,10 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
     name_addr = internet_address_to_string (ia, FALSE);
 
     if (format->is_text_printer) {
+	if (count > 0) {
+	    format->integer (format, count);
+	    format->string (format, "\t");
+	}
 	format->string (format, name_addr);
 	format->separator (format);
     } else {
@@ -286,6 +300,10 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
 	format->string (format, addr);
 	format->map_key (format, "name-addr");
 	format->string (format, name_addr);
+	if (count > 0) {
+	    format->map_key (format, "count");
+	    format->integer (format, count);
+	}
 	format->end (format);
 	format->separator (format);
     }
@@ -294,7 +312,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
     g_free (name_addr);
 }
 
-/* Print addresses from InternetAddressList.  */
+/* Print or prepare for printing addresses from InternetAddressList. */
 static void
 process_address_list (const search_context_t *ctx,
 		      InternetAddressList *list)
@@ -319,17 +337,21 @@ process_address_list (const search_context_t *ctx,
 	    mailbox_t mbx = {
 		.name = internet_address_get_name (address),
 		.addr = internet_address_mailbox_get_addr (mailbox),
+		.count = 0,
 	    };
 
 	    if (is_duplicate (ctx, mbx.name, mbx.addr))
 		continue;
 
+	    if (ctx->output & OUTPUT_COUNT)
+		continue;
+
 	    print_mailbox (ctx, &mbx);
 	}
     }
 }
 
-/* Print addresses from a message header.  */
+/* Print or prepare for printing addresses from a message header. */
 static void
 process_address_header (const search_context_t *ctx, const char *value)
 {
@@ -353,6 +375,15 @@ _my_talloc_free_for_g_hash (void *ptr)
     talloc_free (ptr);
 }
 
+static void
+print_hash_value (unused (gpointer key), gpointer value, gpointer user_data)
+{
+    const mailbox_t *mailbox = value;
+    search_context_t *ctx = user_data;
+
+    print_mailbox (ctx, mailbox);
+}
+
 static int
 do_search_messages (search_context_t *ctx)
 {
@@ -427,6 +458,9 @@ do_search_messages (search_context_t *ctx)
 	notmuch_message_destroy (message);
     }
 
+    if (ctx->addresses && ctx->output & OUTPUT_COUNT)
+	g_hash_table_foreach (ctx->addresses, print_hash_value, ctx);
+
     notmuch_messages_destroy (messages);
 
     format->end (format);
@@ -658,6 +692,7 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
 	{ NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
 	  (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
 				  { "recipients", OUTPUT_RECIPIENTS },
+				  { "count", OUTPUT_COUNT },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_INHERIT, &common_options, NULL, 0, 0 },
 	{ 0, 0, 0, 0, 0 }
@@ -675,7 +710,7 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
 	return EXIT_FAILURE;
 
     ctx->addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
-					    _my_talloc_free_for_g_hash, NULL);
+					    _my_talloc_free_for_g_hash, _my_talloc_free_for_g_hash);
 
     ret = do_search_messages (ctx);
 
diff --git a/test/T095-address.sh b/test/T095-address.sh
index 8a256d2..92e17b0 100755
--- a/test/T095-address.sh
+++ b/test/T095-address.sh
@@ -96,5 +96,53 @@ notmuch address --output=sender --output=recipients '*' >OUTPUT
 # Use EXPECTED from previous subtest
 test_expect_equal_file OUTPUT EXPECTED
 
+test_begin_subtest "--output=sender --output=count"
+notmuch address --output=sender --output=count '*' | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1	Adrian Perez de Castro <aperez@igalia.com>
+1	Aron Griffis <agriffis@n01se.net>
+1	Chris Wilson <chris@chris-wilson.co.uk>
+1	François Boulogne <boulogne.f@gmail.com>
+1	Ingmar Vanhassel <ingmar@exherbo.org>
+1	Israel Herraiz <isra@herraiz.org>
+1	Olivier Berger <olivier.berger@it-sudparis.eu>
+1	Rolland Santimano <rollandsantimano@yahoo.com>
+2	Alex Botero-Lowry <alex.boterolowry@gmail.com>
+2	Jjgod Jiang <gzjjgod@gmail.com>
+3	Stewart Smith <stewart@flamingspork.com>
+4	Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+4	Jan Janak <jan@ryngle.com>
+5	Lars Kellogg-Stedman <lars@seas.harvard.edu>
+5	Mikhail Gusarov <dottedmag@dottedmag.net>
+7	Keith Packard <keithp@keithp.com>
+12	Carl Worth <cworth@cworth.org>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=sender --output=count --format=json"
+# Since the iteration order of GHashTable is not specified, we
+# preprocess and sort the results to keep the order stable here.
+notmuch address --output=sender --output=count --format=json '*' | \
+    sed -e 's/^\[//' -e 's/]$//' -e 's/,$//' | sort >OUTPUT
+cat <<EOF >EXPECTED
+{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com", "name-addr": "Adrian Perez de Castro <aperez@igalia.com>", "count": 1}
+{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alex Botero-Lowry <alex.boterolowry@gmail.com>", "count": 2}
+{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alexander Botero-Lowry <alex.boterolowry@gmail.com>", "count": 4}
+{"name": "Aron Griffis", "address": "agriffis@n01se.net", "name-addr": "Aron Griffis <agriffis@n01se.net>", "count": 1}
+{"name": "Carl Worth", "address": "cworth@cworth.org", "name-addr": "Carl Worth <cworth@cworth.org>", "count": 12}
+{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk", "name-addr": "Chris Wilson <chris@chris-wilson.co.uk>", "count": 1}
+{"name": "François Boulogne", "address": "boulogne.f@gmail.com", "name-addr": "François Boulogne <boulogne.f@gmail.com>", "count": 1}
+{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org", "name-addr": "Ingmar Vanhassel <ingmar@exherbo.org>", "count": 1}
+{"name": "Israel Herraiz", "address": "isra@herraiz.org", "name-addr": "Israel Herraiz <isra@herraiz.org>", "count": 1}
+{"name": "Jan Janak", "address": "jan@ryngle.com", "name-addr": "Jan Janak <jan@ryngle.com>", "count": 4}
+{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com", "name-addr": "Jjgod Jiang <gzjjgod@gmail.com>", "count": 2}
+{"name": "Keith Packard", "address": "keithp@keithp.com", "name-addr": "Keith Packard <keithp@keithp.com>", "count": 7}
+{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu", "name-addr": "Lars Kellogg-Stedman <lars@seas.harvard.edu>", "count": 5}
+{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net", "name-addr": "Mikhail Gusarov <dottedmag@dottedmag.net>", "count": 5}
+{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu", "name-addr": "Olivier Berger <olivier.berger@it-sudparis.eu>", "count": 1}
+{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com", "name-addr": "Rolland Santimano <rollandsantimano@yahoo.com>", "count": 1}
+{"name": "Stewart Smith", "address": "stewart@flamingspork.com", "name-addr": "Stewart Smith <stewart@flamingspork.com>", "count": 3}
+EOF
+test_expect_equal_file OUTPUT EXPECTED
 
 test_done
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 10/10] cli: address: Add --filter-by option to configure address filtering
  2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
                   ` (8 preceding siblings ...)
  2014-11-02  0:49 ` [PATCH 09/10] cli: address: Add --output=count Michal Sojka
@ 2014-11-02  0:50 ` Michal Sojka
  9 siblings, 0 replies; 11+ messages in thread
From: Michal Sojka @ 2014-11-02  0:50 UTC (permalink / raw)
  To: notmuch

This option allows to configure the criterion for duplicate address
filtering. Without this option, all unique combinations of name and
address parts are printed. This option allows to filter the output
more, for example to only contain unique address parts.
---
 completion/notmuch-completion.bash |  6 +++-
 completion/notmuch-completion.zsh  |  1 +
 doc/man1/notmuch-address.rst       | 36 ++++++++++++++++++-
 notmuch-search.c                   | 48 +++++++++++++++++++++++--
 test/T097-address-filter-by.sh     | 73 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 160 insertions(+), 4 deletions(-)
 create mode 100755 test/T097-address-filter-by.sh

diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash
index db152f3..2cb1586 100644
--- a/completion/notmuch-completion.bash
+++ b/completion/notmuch-completion.bash
@@ -310,7 +310,7 @@ _notmuch_search()
     ! $split &&
     case "${cur}" in
 	-*)
-	    local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
+	    local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= --filter-by="
 	    compopt -o nospace
 	    COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
 	    ;;
@@ -343,6 +343,10 @@ _notmuch_address()
 	    COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
 	    return
 	    ;;
+	--filter-by)
+	    COMPREPLY=( $( compgen -W "nameaddr name addr addrfold nameaddrfold" -- "${cur}" ) )
+	    return
+	    ;;
     esac
 
     ! $split &&
diff --git a/completion/notmuch-completion.zsh b/completion/notmuch-completion.zsh
index 8968562..3758f1a 100644
--- a/completion/notmuch-completion.zsh
+++ b/completion/notmuch-completion.zsh
@@ -62,6 +62,7 @@ _notmuch_address()
   _arguments -s : \
     '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
     '--output=[select what to output]:output:((sender recipients count))'
+    '--filter-by=[filter out duplicate addresses]:filter-by:((nameaddr\:"both name and address part" name\:"name part" addr\:"address part" addrfold\:"case-insensitive address part" nameaddrfold\:"name and case-insensitive address part"))'
 }
 
 _notmuch()
diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst
index 18473a7..524ab91 100644
--- a/doc/man1/notmuch-address.rst
+++ b/doc/man1/notmuch-address.rst
@@ -11,7 +11,8 @@ DESCRIPTION
 ===========
 
 Search for messages matching the given search terms, and display the
-addresses from them. Duplicate addresses are filtered out.
+addresses from them. Duplicate addresses are filtered out. Filtering
+can be configured with the --filter-by option.
 
 See **notmuch-search-terms(7)** for details of the supported syntax for
 <search-terms>.
@@ -85,6 +86,39 @@ Supported options for **address** include
         is the number of matching non-excluded messages in the thread,
         rather than the number of matching messages.
 
+    ``--filter-by=``\ (**nameaddr**\ \|\ **name** \|\ **addr**\ \|\ **addrfold**\ \|\ **nameaddrfold**\)
+
+	Controls how to filter out duplicate addresses. The filtering
+	algorithm receives a sequence of email addresses and outputs
+	the same sequence without the addresses that are considered a
+	duplicate of a previously output address. What is considered a
+	duplicate depends on how the two addresses are compared:
+
+	**nameaddr** means that both name and address parts are
+	compared in case-sensitive manner. Therefore, all same looking
+	addresses strings are considered duplicate. This is the
+	default.
+
+	**name** means that only the name part is compared (in
+	case-sensitive manner). For example, the addresses "John Doe
+	<me@example.com>" and "John Doe <john@doe.name>" will be
+	considered duplicate.
+
+	**addr** means that only the address part is compared (in
+	case-sensitive manner). For example, the addresses "John Doe
+	<john@example.com>" and "Dr. John Doe <john@example.com>" will
+	be considered duplicate.
+
+	**addrfold** is like **addr**, but comparison is done in
+	canse-insensitive manner. For example, the addresses "John Doe
+	<john@example.com>" and "Dr. John Doe <JOHN@EXAMPLE.COM>" will
+	be considered duplicate.
+
+	**nameaddrfold** is like **nameaddr**, but address comparison
+	is done in canse-insensitive manner. For example, the
+	addresses "John Doe <john@example.com>" and "John Doe
+	<JOHN@EXAMPLE.COM>" will be considered duplicate.
+
 EXIT STATUS
 ===========
 
diff --git a/notmuch-search.c b/notmuch-search.c
index b45d480..ee1aead 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -43,6 +43,14 @@ typedef enum {
     NOTMUCH_FORMAT_SEXP
 } format_sel_t;
 
+typedef enum {
+    FILTER_BY_NAMEADDR = 0,
+    FILTER_BY_NAME,
+    FILTER_BY_ADDR,
+    FILTER_BY_ADDRFOLD,
+    FILTER_BY_NAMEADDRFOLD,
+} filter_by_t;
+
 typedef struct {
     notmuch_database_t *notmuch;
     format_sel_t format_sel;
@@ -55,6 +63,7 @@ typedef struct {
     int limit;
     int dupe;
     GHashTable *addresses;
+    filter_by_t filter_by;
 } search_context_t;
 
 typedef struct {
@@ -243,15 +252,42 @@ do_search_threads (search_context_t *ctx)
     return 0;
 }
 
-/* Returns TRUE iff name and addr is duplicate. */
+/* Returns TRUE iff name and/or addr is considered duplicate. */
 static notmuch_bool_t
 is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
 {
     notmuch_bool_t duplicate;
     char *key;
+    gchar *addrfold = NULL;
     mailbox_t *mailbox;
 
-    key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
+    if (ctx->filter_by == FILTER_BY_ADDRFOLD ||
+	ctx->filter_by == FILTER_BY_NAMEADDRFOLD)
+	addrfold = g_utf8_casefold (addr, -1);
+
+    switch (ctx->filter_by) {
+    case FILTER_BY_NAMEADDR:
+	key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
+	break;
+    case FILTER_BY_NAMEADDRFOLD:
+	key = talloc_asprintf (ctx->format, "%s <%s>", name, addrfold);
+	break;
+    case FILTER_BY_NAME:
+	key = talloc_strdup (ctx->format, name); /* !name results in !key */
+	break;
+    case FILTER_BY_ADDR:
+	key = talloc_strdup (ctx->format, addr);
+	break;
+    case FILTER_BY_ADDRFOLD:
+	key = talloc_strdup (ctx->format, addrfold);
+	break;
+    default:
+	INTERNAL_ERROR("invalid --filter-by flags");
+    }
+
+    if (addrfold)
+	g_free (addrfold);
+
     if (! key)
 	return FALSE;
 
@@ -694,10 +730,18 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
 				  { "recipients", OUTPUT_RECIPIENTS },
 				  { "count", OUTPUT_COUNT },
 				  { 0, 0 } } },
+	{ NOTMUCH_OPT_KEYWORD, &ctx->filter_by, "filter-by", 'b',
+	  (notmuch_keyword_t []){ { "nameaddr", FILTER_BY_NAMEADDR },
+				  { "name", FILTER_BY_NAME },
+				  { "addr", FILTER_BY_ADDR },
+				  { "addrfold", FILTER_BY_ADDRFOLD },
+				  { "nameaddrfold", FILTER_BY_NAMEADDRFOLD },
+				  { 0, 0 } } },
 	{ NOTMUCH_OPT_INHERIT, &common_options, NULL, 0, 0 },
 	{ 0, 0, 0, 0, 0 }
     };
 
+    ctx->filter_by = FILTER_BY_NAMEADDR,
     opt_index = parse_arguments (argc, argv, options, 1);
     if (opt_index < 0)
 	return EXIT_FAILURE;
diff --git a/test/T097-address-filter-by.sh b/test/T097-address-filter-by.sh
new file mode 100755
index 0000000..544d8e8
--- /dev/null
+++ b/test/T097-address-filter-by.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+test_description='duplicite address filtering in "notmuch address"'
+. ./test-lib.sh
+
+add_message '[to]="John Doe <foo@example.com>, John Doe <bar@example.com>"'
+add_message '[to]="\"Doe, John\" <foo@example.com>"' '[cc]="John Doe <Bar@Example.COM>"'
+add_message '[to]="\"Doe, John\" <foo@example.com>"' '[bcc]="John Doe <Bar@Example.COM>"'
+
+test_begin_subtest "--output=recipients"
+notmuch address --output=recipients "*" >OUTPUT
+cat <<EOF >EXPECTED
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+"Doe, John" <foo@example.com>
+John Doe <Bar@Example.COM>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=nameaddr"
+notmuch address --output=recipients --filter-by=nameaddr "*" >OUTPUT
+# The same as above
+cat <<EOF >EXPECTED
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+"Doe, John" <foo@example.com>
+John Doe <Bar@Example.COM>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=name"
+notmuch address --output=recipients --filter-by=name "*" >OUTPUT
+cat <<EOF >EXPECTED
+John Doe <foo@example.com>
+"Doe, John" <foo@example.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=addr"
+notmuch address --output=recipients --filter-by=addr "*" >OUTPUT
+cat <<EOF >EXPECTED
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+John Doe <Bar@Example.COM>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=addrfold"
+notmuch address --output=recipients --filter-by=addrfold "*" >OUTPUT
+cat <<EOF >EXPECTED
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=nameaddrfold"
+notmuch address --output=recipients --filter-by=nameaddrfold "*" >OUTPUT
+cat <<EOF >EXPECTED
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+"Doe, John" <foo@example.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=nameaddrfold --output=count"
+notmuch address --output=recipients --filter-by=nameaddrfold --output=count "*" | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1	John Doe <foo@example.com>
+2	"Doe, John" <foo@example.com>
+3	John Doe <bar@example.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_done
-- 
2.1.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2014-11-02  0:50 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-11-02  0:49 [PATCH 00/10] "notmuch address" command Michal Sojka
2014-11-02  0:49 ` [PATCH 01/10] cli: search: Rename options to context Michal Sojka
2014-11-02  0:49 ` [PATCH 02/10] cli: search: Move more variables into search_context_t Michal Sojka
2014-11-02  0:49 ` [PATCH 03/10] cli: search: Convert ctx. to ctx-> Michal Sojka
2014-11-02  0:49 ` [PATCH 04/10] cli: search: Split notmuch_search_command to smaller functions Michal Sojka
2014-11-02  0:49 ` [PATCH 05/10] cli: add support for hierarchical command line option arrays Michal Sojka
2014-11-02  0:49 ` [PATCH 06/10] cli: Introduce "notmuch address" command Michal Sojka
2014-11-02  0:49 ` [PATCH 07/10] cli: search: Convert --output to keyword argument Michal Sojka
2014-11-02  0:49 ` [PATCH 08/10] cli: address: Do not output duplicate addresses Michal Sojka
2014-11-02  0:49 ` [PATCH 09/10] cli: address: Add --output=count Michal Sojka
2014-11-02  0:50 ` [PATCH 10/10] cli: address: Add --filter-by option to configure address filtering Michal Sojka

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).