]> rtime.felk.cvut.cz Git - notmuch.git/commitdiff
cli: Add configurable address deduplication for --output=addresses
authorMichal Sojka <sojkam1@fel.cvut.cz>
Sun, 21 Sep 2014 18:45:14 +0000 (20:45 +0200)
committerMichal Sojka <sojkam1@fel.cvut.cz>
Mon, 22 Sep 2014 08:36:36 +0000 (10:36 +0200)
The code here is an extended version of a path from Jani Nikula.

completion/notmuch-completion.bash
completion/notmuch-completion.zsh
doc/man1/notmuch-search.rst
notmuch-search.c

index c37ddf56233bb3f8040e870eb30ec37d5373e7ab..8bc78740af1a378ffb72ca0cebdd4038f6032d33 100644 (file)
@@ -305,12 +305,16 @@ _notmuch_search()
            COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
            return
            ;;
            COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
            return
            ;;
+       --unique)
+           COMPREPLY=( $( compgen -W "none addr addrfold name" -- "${cur}" ) )
+           return
+           ;;
     esac
 
     ! $split &&
     case "${cur}" in
        -*)
     esac
 
     ! $split &&
     case "${cur}" in
        -*)
-           local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
+           local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= --unique="
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
index bff8fd5bbf6240deada0255a8bd3188c85f92f70..cf4968cc8a6bdecba3f97c9b634869bbae9d9305 100644 (file)
@@ -53,7 +53,8 @@ _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"))' \
     '--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 addresses))'
+    '--output=[select what to output]:output:((summary threads messages files tags sender recipients addresses))' \
+    '--unique=[address deduplication]:unique:((none\:"no deduplication" addr\:"deduplicate by address" addrfold\:"deduplicate by case-insensitive address" name\:"deduplicate by name"))'
 }
 
 _notmuch()
 }
 
 _notmuch()
index 6094906822c1f15e51af1bf7da2bbfe40cd3cfc5..a92779a701d833ca4b356dd5d880ded09d0392a5 100644 (file)
@@ -85,6 +85,9 @@ Supported options for **search** include
             (--format=text0), as a JSON array (--format=json), or as
             an S-Expression list (--format=sexp).
 
             (--format=text0), as a JSON array (--format=json), or as
             an S-Expression list (--format=sexp).
 
+            Handling of duplicate addresses and/or names can be
+            controlled with the --unique option.
+
            Note: Searching for **sender** should much be faster than
            searching for **recipients** or **addresses**, because
            sender addresses are cached directly in the database
            Note: Searching for **sender** should much be faster than
            searching for **recipients** or **addresses**, because
            sender addresses are cached directly in the database
@@ -151,6 +154,36 @@ Supported options for **search** include
         prefix. The prefix matches messages based on filenames. This
         option filters filenames of the matching messages.
 
         prefix. The prefix matches messages based on filenames. This
         option filters filenames of the matching messages.
 
+    ``--unique=``\ (**none**\ \|\ **addr**\ \|\ **addrfold**\ \|\ **name**)[,\ ...]
+
+        Can be used with ``--output=addresses``, ``--output=sender``
+        or ``--output=recipients`` to control the address
+        deduplication algorithm.
+
+       **none** means that no deduplication is performed. The same
+       address can appear multiple times in the output.
+
+       **addr** means that case-sensitive deduplication is performed
+       on the address part. For example, given the addresses "John
+       Doe <john@example.com>" and "Dr. John Doe <john@example.com>",
+       only one will be printed.
+
+       **addrfold** means that case-insensitive deduplication is
+       performed on the address part. For example, given the
+       addresses "John Doe <john@example.com>" and "John Doe
+       <JOHN@EXAMPLE.COM>", only one will be printed. This is the
+       default.
+
+       **name** means that case-sensitive deduplication is performed
+       on the name part. For example, given the addresses "John Doe
+       <john@example.com>" and "John Doe <john@doe.name>", only one
+       will be printed.
+
+       It is possible to combine the above flags (except **none**) by
+       separating them with comma. For example,
+       ``--unique=name,addr`` will print unique case-sensitive
+       combinations of name and address.
+
 EXIT STATUS
 ===========
 
 EXIT STATUS
 ===========
 
index 0614f1023b88da5f3259329d37dd79d4bd37c9da..00d6771b9a3f3533abb6aa511d7a1fb32a4a9b97 100644 (file)
@@ -33,6 +33,15 @@ typedef enum {
     OUTPUT_ADDRESSES   = OUTPUT_SENDER | OUTPUT_RECIPIENTS,
 } output_t;
 
     OUTPUT_ADDRESSES   = OUTPUT_SENDER | OUTPUT_RECIPIENTS,
 } output_t;
 
+typedef enum {
+    UNIQUE_NONE          = 1 << 0,
+    UNIQUE_ADDR          = 1 << 1,
+    UNIQUE_NAME          = 1 << 2,
+    UNIQUE_ADDR_CASEFOLD  = 1 << 3,
+
+    UNIQUE_BOTH = UNIQUE_NAME | UNIQUE_ADDR,
+} unique_t;
+
 typedef struct {
     sprinter_t *format;
     notmuch_query_t *query;
 typedef struct {
     sprinter_t *format;
     notmuch_query_t *query;
@@ -41,6 +50,7 @@ typedef struct {
     int offset;
     int limit;
     int dupe;
     int offset;
     int limit;
     int dupe;
+    unique_t unique;
 } search_options_t;
 
 /* Return two stable query strings that identify exactly the matched
 } search_options_t;
 
 /* Return two stable query strings that identify exactly the matched
@@ -223,8 +233,51 @@ do_search_threads (search_options_t *o)
     return 0;
 }
 
     return 0;
 }
 
+/* Returns TRUE iff name and/or addr is considered unique. */
+static notmuch_bool_t
+check_unique (const search_options_t *o, GHashTable *addrs, const char *name, const char *addr)
+{
+    notmuch_bool_t unique;
+    char *key;
+
+    if (o->unique == UNIQUE_NONE)
+       return TRUE;
+
+    if (o->unique & UNIQUE_ADDR_CASEFOLD) {
+       gchar *folded = g_utf8_casefold (addr, -1);
+       addr = talloc_strdup (o->format, folded);
+       g_free (folded);
+    }
+    switch (o->unique & UNIQUE_BOTH) {
+    case UNIQUE_NAME:
+       key = talloc_strdup (o->format, name); /* !name results in !key */
+       break;
+    case UNIQUE_ADDR:
+       key = talloc_strdup (o->format, addr);
+       break;
+    case UNIQUE_BOTH:
+       key = talloc_asprintf (o->format, "%s <%s>", name, addr);
+       break;
+    default:
+       INTERNAL_ERROR("invalid --unique flags");
+    }
+
+    if (! key)
+       return FALSE;
+
+    unique = !g_hash_table_lookup_extended (addrs, key, NULL, NULL);
+
+    if (unique)
+       g_hash_table_insert (addrs, key, NULL);
+    else
+       talloc_free (key);
+
+    return unique;
+}
+
 static void
 static void
-print_address_list (const search_options_t *o, InternetAddressList *list)
+print_address_list (const search_options_t *o, GHashTable *addrs,
+                   InternetAddressList *list)
 {
     InternetAddress *address;
     int i;
 {
     InternetAddress *address;
     int i;
@@ -240,7 +293,7 @@ print_address_list (const search_options_t *o, InternetAddressList *list)
            if (group_list == NULL)
                continue;
 
            if (group_list == NULL)
                continue;
 
-           print_address_list (o, group_list);
+           print_address_list (o, addrs, group_list);
        } else {
            InternetAddressMailbox *mailbox;
            const char *name;
        } else {
            InternetAddressMailbox *mailbox;
            const char *name;
@@ -252,6 +305,9 @@ print_address_list (const search_options_t *o, InternetAddressList *list)
            name = internet_address_get_name (address);
            addr = internet_address_mailbox_get_addr (mailbox);
 
            name = internet_address_get_name (address);
            addr = internet_address_mailbox_get_addr (mailbox);
 
+           if (!check_unique (o, addrs, name, addr))
+               continue;
+
            if (name && *name)
                full_address = talloc_asprintf (o->format, "%s <%s>", name, addr);
            else
            if (name && *name)
                full_address = talloc_asprintf (o->format, "%s <%s>", name, addr);
            else
@@ -270,7 +326,7 @@ print_address_list (const search_options_t *o, InternetAddressList *list)
 }
 
 static void
 }
 
 static void
-print_address_string (const search_options_t *o, const char *recipients)
+print_address_string (const search_options_t *o, GHashTable *addrs, const char *recipients)
 {
     InternetAddressList *list;
 
 {
     InternetAddressList *list;
 
@@ -281,7 +337,13 @@ print_address_string (const search_options_t *o, const char *recipients)
     if (list == NULL)
        return;
 
     if (list == NULL)
        return;
 
-    print_address_list (o, list);
+    print_address_list (o, addrs, list);
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
 }
 
 static int
 }
 
 static int
@@ -291,8 +353,13 @@ do_search_messages (search_options_t *o)
     notmuch_messages_t *messages;
     notmuch_filenames_t *filenames;
     sprinter_t *format = o->format;
     notmuch_messages_t *messages;
     notmuch_filenames_t *filenames;
     sprinter_t *format = o->format;
+    GHashTable *addresses = NULL;
     int i;
 
     int i;
 
+    if (o->output & OUTPUT_ADDRESSES)
+       addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                          _my_talloc_free_for_g_hash, NULL);
+
     if (o->offset < 0) {
        o->offset += notmuch_query_count_messages (o->query);
        if (o->offset < 0)
     if (o->offset < 0) {
        o->offset += notmuch_query_count_messages (o->query);
        if (o->offset < 0)
@@ -340,7 +407,7 @@ do_search_messages (search_options_t *o)
                const char *addrs;
 
                addrs = notmuch_message_get_header (message, "from");
                const char *addrs;
 
                addrs = notmuch_message_get_header (message, "from");
-               print_address_string (o, addrs);
+               print_address_string (o, addresses, addrs);
            }
 
            if (o->output & OUTPUT_RECIPIENTS) {
            }
 
            if (o->output & OUTPUT_RECIPIENTS) {
@@ -350,7 +417,7 @@ do_search_messages (search_options_t *o)
 
                for (j = 0; j < ARRAY_SIZE (hdrs); j++) {
                    addrs = notmuch_message_get_header (message, hdrs[j]);
 
                for (j = 0; j < ARRAY_SIZE (hdrs); j++) {
                    addrs = notmuch_message_get_header (message, hdrs[j]);
-                   print_address_string (o, addrs);
+                   print_address_string (o, addresses, addrs);
                }
            }
        }
                }
            }
        }
@@ -358,6 +425,9 @@ do_search_messages (search_options_t *o)
        notmuch_message_destroy (message);
     }
 
        notmuch_message_destroy (message);
     }
 
+    if (addresses)
+       g_hash_table_unref (addresses);
+
     notmuch_messages_destroy (messages);
 
     format->end (format);
     notmuch_messages_destroy (messages);
 
     format->end (format);
@@ -423,6 +493,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
        .offset = 0,
        .limit = -1, /* unlimited */
        .dupe = -1,
        .offset = 0,
        .limit = -1, /* unlimited */
        .dupe = -1,
+       .unique = 0,
     };
     char *query_str;
     int opt_index, ret;
     };
     char *query_str;
     int opt_index, ret;
@@ -467,6 +538,12 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
        { NOTMUCH_OPT_INT, &o.offset, "offset", 'O', 0 },
        { NOTMUCH_OPT_INT, &o.limit, "limit", 'L', 0  },
        { NOTMUCH_OPT_INT, &o.dupe, "duplicate", 'D', 0  },
        { NOTMUCH_OPT_INT, &o.offset, "offset", 'O', 0 },
        { NOTMUCH_OPT_INT, &o.limit, "limit", 'L', 0  },
        { NOTMUCH_OPT_INT, &o.dupe, "duplicate", 'D', 0  },
+       { NOTMUCH_OPT_FLAGS, &o.unique, "unique", 'u',
+         (notmuch_keyword_t []){ { "none", UNIQUE_NONE },
+                                 { "name", UNIQUE_NAME },
+                                 { "addr", UNIQUE_ADDR },
+                                 { "addrfold", UNIQUE_ADDR | UNIQUE_ADDR_CASEFOLD },
+                                 { 0, 0 } } },
        { 0, 0, 0, 0, 0 }
     };
 
        { 0, 0, 0, 0, 0 }
     };
 
@@ -474,6 +551,18 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
     if (opt_index < 0)
        return EXIT_FAILURE;
 
     if (opt_index < 0)
        return EXIT_FAILURE;
 
+    if (o.unique && (o.output & ~OUTPUT_ADDRESSES)) {
+       fprintf (stderr, "Error: --unique can only be used with address output.\n");
+       return EXIT_FAILURE;
+    }
+    if ((o.unique & UNIQUE_NONE) &&
+       (o.unique & ~UNIQUE_NONE)) {
+       fprintf (stderr, "Error: --unique=none cannot be combined with other flags.\n");
+       return EXIT_FAILURE;
+    }
+    if (! o.unique)
+       o.unique = UNIQUE_ADDR | UNIQUE_ADDR_CASEFOLD;
+
     switch (format_sel) {
     case NOTMUCH_FORMAT_TEXT:
        o.format = sprinter_text_create (config, stdout);
     switch (format_sel) {
     case NOTMUCH_FORMAT_TEXT:
        o.format = sprinter_text_create (config, stdout);