1 Return-Path: <sojkam1@fel.cvut.cz>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id 303F0431FD7
\r
6 for <notmuch@notmuchmail.org>; Mon, 22 Sep 2014 02:39:42 -0700 (PDT)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=-2.3 tagged_above=-999 required=5
\r
12 tests=[RCVD_IN_DNSWL_MED=-2.3] autolearn=disabled
\r
13 Received: from olra.theworths.org ([127.0.0.1])
\r
14 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
15 with ESMTP id WeHv52tSb0Pp for <notmuch@notmuchmail.org>;
\r
16 Mon, 22 Sep 2014 02:39:36 -0700 (PDT)
\r
17 Received: from max.feld.cvut.cz (max.feld.cvut.cz [147.32.192.36])
\r
18 by olra.theworths.org (Postfix) with ESMTP id 2DB10431FC0
\r
19 for <notmuch@notmuchmail.org>; Mon, 22 Sep 2014 02:39:28 -0700 (PDT)
\r
20 Received: from localhost (unknown [192.168.200.7])
\r
21 by max.feld.cvut.cz (Postfix) with ESMTP id 548395CCE5F;
\r
22 Mon, 22 Sep 2014 11:39:27 +0200 (CEST)
\r
23 X-Virus-Scanned: IMAP STYX AMAVIS
\r
24 Received: from max.feld.cvut.cz ([192.168.200.1])
\r
25 by localhost (styx.feld.cvut.cz [192.168.200.7]) (amavisd-new,
\r
27 with ESMTP id vZIpkuAutMQp; Mon, 22 Sep 2014 11:39:22 +0200 (CEST)
\r
28 Received: from imap.feld.cvut.cz (imap.feld.cvut.cz [147.32.192.34])
\r
29 by max.feld.cvut.cz (Postfix) with ESMTP id E48495CCE68;
\r
30 Mon, 22 Sep 2014 11:39:22 +0200 (CEST)
\r
31 Received: from wsh by steelpick.2x.cz with local (Exim 4.84)
\r
32 (envelope-from <sojkam1@fel.cvut.cz>)
\r
33 id 1XW05U-0001wW-PE; Mon, 22 Sep 2014 11:39:16 +0200
\r
34 From: Michal Sojka <sojkam1@fel.cvut.cz>
\r
35 To: notmuch@notmuchmail.org
\r
36 Subject: [PATCH 4/5] cli: Add configurable address deduplication for
\r
38 Date: Mon, 22 Sep 2014 11:37:58 +0200
\r
39 Message-Id: <1411378679-7307-5-git-send-email-sojkam1@fel.cvut.cz>
\r
40 X-Mailer: git-send-email 2.1.0
\r
41 In-Reply-To: <1411378679-7307-1-git-send-email-sojkam1@fel.cvut.cz>
\r
42 References: <1411378679-7307-1-git-send-email-sojkam1@fel.cvut.cz>
\r
43 X-BeenThere: notmuch@notmuchmail.org
\r
44 X-Mailman-Version: 2.1.13
\r
46 List-Id: "Use and development of the notmuch mail system."
\r
47 <notmuch.notmuchmail.org>
\r
48 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
49 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
50 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
51 List-Post: <mailto:notmuch@notmuchmail.org>
\r
52 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
53 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
54 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
55 X-List-Received-Date: Mon, 22 Sep 2014 09:39:42 -0000
\r
57 The code here is an extended version of a path from Jani Nikula.
\r
59 completion/notmuch-completion.bash | 6 ++-
\r
60 completion/notmuch-completion.zsh | 3 +-
\r
61 doc/man1/notmuch-search.rst | 33 ++++++++++++
\r
62 notmuch-search.c | 101 ++++++++++++++++++++++++++++++++++---
\r
63 4 files changed, 135 insertions(+), 8 deletions(-)
\r
65 diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash
\r
66 index c37ddf5..8bc7874 100644
\r
67 --- a/completion/notmuch-completion.bash
\r
68 +++ b/completion/notmuch-completion.bash
\r
69 @@ -305,12 +305,16 @@ _notmuch_search()
\r
70 COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
\r
74 + COMPREPLY=( $( compgen -W "none addr addrfold name" -- "${cur}" ) )
\r
82 - local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
\r
83 + local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= --unique="
\r
85 COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
\r
87 diff --git a/completion/notmuch-completion.zsh b/completion/notmuch-completion.zsh
\r
88 index bff8fd5..cf4968c 100644
\r
89 --- a/completion/notmuch-completion.zsh
\r
90 +++ b/completion/notmuch-completion.zsh
\r
91 @@ -53,7 +53,8 @@ _notmuch_search()
\r
92 '--max-threads=[display only the first x threads from the search results]:number of threads to show: ' \
\r
93 '--first=[omit the first x threads from the search results]:number of threads to omit: ' \
\r
94 '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
\r
95 - '--output=[select what to output]:output:((summary threads messages files tags sender recipients addresses))'
\r
96 + '--output=[select what to output]:output:((summary threads messages files tags sender recipients addresses))' \
\r
97 + '--unique=[address deduplication]:unique:((none\:"no deduplication" addr\:"deduplicate by address" addrfold\:"deduplicate by case-insensitive address" name\:"deduplicate by name"))'
\r
101 diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst
\r
102 index 6094906..a92779a 100644
\r
103 --- a/doc/man1/notmuch-search.rst
\r
104 +++ b/doc/man1/notmuch-search.rst
\r
105 @@ -85,6 +85,9 @@ Supported options for **search** include
\r
106 (--format=text0), as a JSON array (--format=json), or as
\r
107 an S-Expression list (--format=sexp).
\r
109 + Handling of duplicate addresses and/or names can be
\r
110 + controlled with the --unique option.
\r
112 Note: Searching for **sender** should much be faster than
\r
113 searching for **recipients** or **addresses**, because
\r
114 sender addresses are cached directly in the database
\r
115 @@ -151,6 +154,36 @@ Supported options for **search** include
\r
116 prefix. The prefix matches messages based on filenames. This
\r
117 option filters filenames of the matching messages.
\r
119 + ``--unique=``\ (**none**\ \|\ **addr**\ \|\ **addrfold**\ \|\ **name**)[,\ ...]
\r
121 + Can be used with ``--output=addresses``, ``--output=sender``
\r
122 + or ``--output=recipients`` to control the address
\r
123 + deduplication algorithm.
\r
125 + **none** means that no deduplication is performed. The same
\r
126 + address can appear multiple times in the output.
\r
128 + **addr** means that case-sensitive deduplication is performed
\r
129 + on the address part. For example, given the addresses "John
\r
130 + Doe <john@example.com>" and "Dr. John Doe <john@example.com>",
\r
131 + only one will be printed.
\r
133 + **addrfold** means that case-insensitive deduplication is
\r
134 + performed on the address part. For example, given the
\r
135 + addresses "John Doe <john@example.com>" and "John Doe
\r
136 + <JOHN@EXAMPLE.COM>", only one will be printed. This is the
\r
139 + **name** means that case-sensitive deduplication is performed
\r
140 + on the name part. For example, given the addresses "John Doe
\r
141 + <john@example.com>" and "John Doe <john@doe.name>", only one
\r
144 + It is possible to combine the above flags (except **none**) by
\r
145 + separating them with comma. For example,
\r
146 + ``--unique=name,addr`` will print unique case-sensitive
\r
147 + combinations of name and address.
\r
152 diff --git a/notmuch-search.c b/notmuch-search.c
\r
153 index 0614f10..00d6771 100644
\r
154 --- a/notmuch-search.c
\r
155 +++ b/notmuch-search.c
\r
156 @@ -33,6 +33,15 @@ typedef enum {
\r
157 OUTPUT_ADDRESSES = OUTPUT_SENDER | OUTPUT_RECIPIENTS,
\r
161 + UNIQUE_NONE = 1 << 0,
\r
162 + UNIQUE_ADDR = 1 << 1,
\r
163 + UNIQUE_NAME = 1 << 2,
\r
164 + UNIQUE_ADDR_CASEFOLD = 1 << 3,
\r
166 + UNIQUE_BOTH = UNIQUE_NAME | UNIQUE_ADDR,
\r
170 sprinter_t *format;
\r
171 notmuch_query_t *query;
\r
172 @@ -41,6 +50,7 @@ typedef struct {
\r
177 } search_options_t;
\r
179 /* Return two stable query strings that identify exactly the matched
\r
180 @@ -223,8 +233,51 @@ do_search_threads (search_options_t *o)
\r
184 +/* Returns TRUE iff name and/or addr is considered unique. */
\r
185 +static notmuch_bool_t
\r
186 +check_unique (const search_options_t *o, GHashTable *addrs, const char *name, const char *addr)
\r
188 + notmuch_bool_t unique;
\r
191 + if (o->unique == UNIQUE_NONE)
\r
194 + if (o->unique & UNIQUE_ADDR_CASEFOLD) {
\r
195 + gchar *folded = g_utf8_casefold (addr, -1);
\r
196 + addr = talloc_strdup (o->format, folded);
\r
199 + switch (o->unique & UNIQUE_BOTH) {
\r
200 + case UNIQUE_NAME:
\r
201 + key = talloc_strdup (o->format, name); /* !name results in !key */
\r
203 + case UNIQUE_ADDR:
\r
204 + key = talloc_strdup (o->format, addr);
\r
206 + case UNIQUE_BOTH:
\r
207 + key = talloc_asprintf (o->format, "%s <%s>", name, addr);
\r
210 + INTERNAL_ERROR("invalid --unique flags");
\r
216 + unique = !g_hash_table_lookup_extended (addrs, key, NULL, NULL);
\r
219 + g_hash_table_insert (addrs, key, NULL);
\r
221 + talloc_free (key);
\r
227 -print_address_list (const search_options_t *o, InternetAddressList *list)
\r
228 +print_address_list (const search_options_t *o, GHashTable *addrs,
\r
229 + InternetAddressList *list)
\r
231 InternetAddress *address;
\r
233 @@ -240,7 +293,7 @@ print_address_list (const search_options_t *o, InternetAddressList *list)
\r
234 if (group_list == NULL)
\r
237 - print_address_list (o, group_list);
\r
238 + print_address_list (o, addrs, group_list);
\r
240 InternetAddressMailbox *mailbox;
\r
242 @@ -252,6 +305,9 @@ print_address_list (const search_options_t *o, InternetAddressList *list)
\r
243 name = internet_address_get_name (address);
\r
244 addr = internet_address_mailbox_get_addr (mailbox);
\r
246 + if (!check_unique (o, addrs, name, addr))
\r
250 full_address = talloc_asprintf (o->format, "%s <%s>", name, addr);
\r
252 @@ -270,7 +326,7 @@ print_address_list (const search_options_t *o, InternetAddressList *list)
\r
256 -print_address_string (const search_options_t *o, const char *recipients)
\r
257 +print_address_string (const search_options_t *o, GHashTable *addrs, const char *recipients)
\r
259 InternetAddressList *list;
\r
261 @@ -281,7 +337,13 @@ print_address_string (const search_options_t *o, const char *recipients)
\r
265 - print_address_list (o, list);
\r
266 + print_address_list (o, addrs, list);
\r
270 +_my_talloc_free_for_g_hash (void *ptr)
\r
272 + talloc_free (ptr);
\r
276 @@ -291,8 +353,13 @@ do_search_messages (search_options_t *o)
\r
277 notmuch_messages_t *messages;
\r
278 notmuch_filenames_t *filenames;
\r
279 sprinter_t *format = o->format;
\r
280 + GHashTable *addresses = NULL;
\r
283 + if (o->output & OUTPUT_ADDRESSES)
\r
284 + addresses = g_hash_table_new_full (g_str_hash, g_str_equal,
\r
285 + _my_talloc_free_for_g_hash, NULL);
\r
287 if (o->offset < 0) {
\r
288 o->offset += notmuch_query_count_messages (o->query);
\r
290 @@ -340,7 +407,7 @@ do_search_messages (search_options_t *o)
\r
293 addrs = notmuch_message_get_header (message, "from");
\r
294 - print_address_string (o, addrs);
\r
295 + print_address_string (o, addresses, addrs);
\r
298 if (o->output & OUTPUT_RECIPIENTS) {
\r
299 @@ -350,7 +417,7 @@ do_search_messages (search_options_t *o)
\r
301 for (j = 0; j < ARRAY_SIZE (hdrs); j++) {
\r
302 addrs = notmuch_message_get_header (message, hdrs[j]);
\r
303 - print_address_string (o, addrs);
\r
304 + print_address_string (o, addresses, addrs);
\r
308 @@ -358,6 +425,9 @@ do_search_messages (search_options_t *o)
\r
309 notmuch_message_destroy (message);
\r
313 + g_hash_table_unref (addresses);
\r
315 notmuch_messages_destroy (messages);
\r
317 format->end (format);
\r
318 @@ -423,6 +493,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
\r
320 .limit = -1, /* unlimited */
\r
325 int opt_index, ret;
\r
326 @@ -467,6 +538,12 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
\r
327 { NOTMUCH_OPT_INT, &o.offset, "offset", 'O', 0 },
\r
328 { NOTMUCH_OPT_INT, &o.limit, "limit", 'L', 0 },
\r
329 { NOTMUCH_OPT_INT, &o.dupe, "duplicate", 'D', 0 },
\r
330 + { NOTMUCH_OPT_FLAGS, &o.unique, "unique", 'u',
\r
331 + (notmuch_keyword_t []){ { "none", UNIQUE_NONE },
\r
332 + { "name", UNIQUE_NAME },
\r
333 + { "addr", UNIQUE_ADDR },
\r
334 + { "addrfold", UNIQUE_ADDR | UNIQUE_ADDR_CASEFOLD },
\r
339 @@ -474,6 +551,18 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
\r
341 return EXIT_FAILURE;
\r
343 + if (o.unique && (o.output & ~OUTPUT_ADDRESSES)) {
\r
344 + fprintf (stderr, "Error: --unique can only be used with address output.\n");
\r
345 + return EXIT_FAILURE;
\r
347 + if ((o.unique & UNIQUE_NONE) &&
\r
348 + (o.unique & ~UNIQUE_NONE)) {
\r
349 + fprintf (stderr, "Error: --unique=none cannot be combined with other flags.\n");
\r
350 + return EXIT_FAILURE;
\r
353 + o.unique = UNIQUE_ADDR | UNIQUE_ADDR_CASEFOLD;
\r
355 switch (format_sel) {
\r
356 case NOTMUCH_FORMAT_TEXT:
\r
357 o.format = sprinter_text_create (config, stdout);
\r