Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / 46 / 2e9d1844a98619c873f0dc07d1597a7dd69706
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
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -2.3\r
10 X-Spam-Level: \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
26         port 10044)\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
37         --output=addresses\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
45 Precedence: list\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
56 \r
57 The code here is an extended version of a path from Jani Nikula.\r
58 ---\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
64 \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
71             return\r
72             ;;\r
73 +       --unique)\r
74 +           COMPREPLY=( $( compgen -W "none addr addrfold name" -- "${cur}" ) )\r
75 +           return\r
76 +           ;;\r
77      esac\r
78  \r
79      ! $split &&\r
80      case "${cur}" in\r
81         -*)\r
82 -           local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="\r
83 +           local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= --unique="\r
84             compopt -o nospace\r
85             COMPREPLY=( $(compgen -W "$options" -- ${cur}) )\r
86             ;;\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
98  }\r
99  \r
100  _notmuch()\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
108  \r
109 +            Handling of duplicate addresses and/or names can be\r
110 +            controlled with the --unique option.\r
111 +\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
118  \r
119 +    ``--unique=``\ (**none**\ \|\ **addr**\ \|\ **addrfold**\ \|\ **name**)[,\ ...]\r
120 +\r
121 +        Can be used with ``--output=addresses``, ``--output=sender``\r
122 +        or ``--output=recipients`` to control the address\r
123 +        deduplication algorithm.\r
124 +\r
125 +       **none** means that no deduplication is performed. The same\r
126 +       address can appear multiple times in the output.\r
127 +\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
132 +\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
137 +       default.\r
138 +\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
142 +       will be printed.\r
143 +\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
148 +\r
149  EXIT STATUS\r
150  ===========\r
151  \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
158  } output_t;\r
159  \r
160 +typedef enum {\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
165 +\r
166 +    UNIQUE_BOTH = UNIQUE_NAME | UNIQUE_ADDR,\r
167 +} unique_t;\r
168 +\r
169  typedef struct {\r
170      sprinter_t *format;\r
171      notmuch_query_t *query;\r
172 @@ -41,6 +50,7 @@ typedef struct {\r
173      int offset;\r
174      int limit;\r
175      int dupe;\r
176 +    unique_t unique;\r
177  } search_options_t;\r
178  \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
181      return 0;\r
182  }\r
183  \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
187 +{\r
188 +    notmuch_bool_t unique;\r
189 +    char *key;\r
190 +\r
191 +    if (o->unique == UNIQUE_NONE)\r
192 +       return TRUE;\r
193 +\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
197 +       g_free (folded);\r
198 +    }\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
202 +       break;\r
203 +    case UNIQUE_ADDR:\r
204 +       key = talloc_strdup (o->format, addr);\r
205 +       break;\r
206 +    case UNIQUE_BOTH:\r
207 +       key = talloc_asprintf (o->format, "%s <%s>", name, addr);\r
208 +       break;\r
209 +    default:\r
210 +       INTERNAL_ERROR("invalid --unique flags");\r
211 +    }\r
212 +\r
213 +    if (! key)\r
214 +       return FALSE;\r
215 +\r
216 +    unique = !g_hash_table_lookup_extended (addrs, key, NULL, NULL);\r
217 +\r
218 +    if (unique)\r
219 +       g_hash_table_insert (addrs, key, NULL);\r
220 +    else\r
221 +       talloc_free (key);\r
222 +\r
223 +    return unique;\r
224 +}\r
225 +\r
226  static void\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
230  {\r
231      InternetAddress *address;\r
232      int i;\r
233 @@ -240,7 +293,7 @@ print_address_list (const search_options_t *o, InternetAddressList *list)\r
234             if (group_list == NULL)\r
235                 continue;\r
236  \r
237 -           print_address_list (o, group_list);\r
238 +           print_address_list (o, addrs, group_list);\r
239         } else {\r
240             InternetAddressMailbox *mailbox;\r
241             const char *name;\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
245  \r
246 +           if (!check_unique (o, addrs, name, addr))\r
247 +               continue;\r
248 +\r
249             if (name && *name)\r
250                 full_address = talloc_asprintf (o->format, "%s <%s>", name, addr);\r
251             else\r
252 @@ -270,7 +326,7 @@ print_address_list (const search_options_t *o, InternetAddressList *list)\r
253  }\r
254  \r
255  static void\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
258  {\r
259      InternetAddressList *list;\r
260  \r
261 @@ -281,7 +337,13 @@ print_address_string (const search_options_t *o, const char *recipients)\r
262      if (list == NULL)\r
263         return;\r
264  \r
265 -    print_address_list (o, list);\r
266 +    print_address_list (o, addrs, list);\r
267 +}\r
268 +\r
269 +static void\r
270 +_my_talloc_free_for_g_hash (void *ptr)\r
271 +{\r
272 +    talloc_free (ptr);\r
273  }\r
274  \r
275  static int\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
281      int i;\r
282  \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
286 +\r
287      if (o->offset < 0) {\r
288         o->offset += notmuch_query_count_messages (o->query);\r
289         if (o->offset < 0)\r
290 @@ -340,7 +407,7 @@ do_search_messages (search_options_t *o)\r
291                 const char *addrs;\r
292  \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
296             }\r
297  \r
298             if (o->output & OUTPUT_RECIPIENTS) {\r
299 @@ -350,7 +417,7 @@ do_search_messages (search_options_t *o)\r
300  \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
305                 }\r
306             }\r
307         }\r
308 @@ -358,6 +425,9 @@ do_search_messages (search_options_t *o)\r
309         notmuch_message_destroy (message);\r
310      }\r
311  \r
312 +    if (addresses)\r
313 +       g_hash_table_unref (addresses);\r
314 +\r
315      notmuch_messages_destroy (messages);\r
316  \r
317      format->end (format);\r
318 @@ -423,6 +493,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])\r
319         .offset = 0,\r
320         .limit = -1, /* unlimited */\r
321         .dupe = -1,\r
322 +       .unique = 0,\r
323      };\r
324      char *query_str;\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
335 +                                 { 0, 0 } } },\r
336         { 0, 0, 0, 0, 0 }\r
337      };\r
338  \r
339 @@ -474,6 +551,18 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])\r
340      if (opt_index < 0)\r
341         return EXIT_FAILURE;\r
342  \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
346 +    }\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
351 +    }\r
352 +    if (! o.unique)\r
353 +       o.unique = UNIQUE_ADDR | UNIQUE_ADDR_CASEFOLD;\r
354 +\r
355      switch (format_sel) {\r
356      case NOTMUCH_FORMAT_TEXT:\r
357         o.format = sprinter_text_create (config, stdout);\r
358 -- \r
359 2.1.0\r
360 \r