[PATCH 6/9] CLI: refactor dumping of tags.
[notmuch-archives.git] / b5 / ff3270941cafc40ab4cd61f982bc7a934e118e
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 C87AF429E32\r
6         for <notmuch@notmuchmail.org>; Sun, 12 Oct 2014 14:42:14 -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 HMcAOEMOAeNd for <notmuch@notmuchmail.org>;\r
16         Sun, 12 Oct 2014 14:42:03 -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 1CCBD431E64\r
19         for <notmuch@notmuchmail.org>; Sun, 12 Oct 2014 14:41:53 -0700 (PDT)\r
20 Received: from localhost (unknown [192.168.200.7])\r
21         by max.feld.cvut.cz (Postfix) with ESMTP id 9EDFE5CCF29\r
22         for <notmuch@notmuchmail.org>; Sun, 12 Oct 2014 23:41:49 +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) with ESMTP id zoBUWYNZpRwa for <notmuch@notmuchmail.org>;\r
27         Sun, 12 Oct 2014 23:41:46 +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 48F545CCF24\r
30         for <notmuch@notmuchmail.org>; Sun, 12 Oct 2014 23:41:45 +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 1XdQtb-0002CI-67; Sun, 12 Oct 2014 23:41:43 +0200\r
34 From: Michal Sojka <sojkam1@fel.cvut.cz>\r
35 To: notmuch@notmuchmail.org\r
36 Subject: [PATCH v3 3/4] cli: Extend the search command for --output={sender,\r
37         recipients}\r
38 Date: Sun, 12 Oct 2014 23:41:32 +0200\r
39 Message-Id: <1413150093-8383-4-git-send-email-sojkam1@fel.cvut.cz>\r
40 X-Mailer: git-send-email 2.1.1\r
41 In-Reply-To: <1413150093-8383-1-git-send-email-sojkam1@fel.cvut.cz>\r
42 References: <87zjd51phx.fsf@steelpick.2x.cz>\r
43         <1413150093-8383-1-git-send-email-sojkam1@fel.cvut.cz>\r
44 MIME-Version: 1.0\r
45 Content-Type: text/plain; charset=UTF-8\r
46 Content-Transfer-Encoding: 8bit\r
47 X-BeenThere: notmuch@notmuchmail.org\r
48 X-Mailman-Version: 2.1.13\r
49 Precedence: list\r
50 List-Id: "Use and development of the notmuch mail system."\r
51         <notmuch.notmuchmail.org>\r
52 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
53         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
54 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
55 List-Post: <mailto:notmuch@notmuchmail.org>\r
56 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
57 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
58         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
59 X-List-Received-Date: Sun, 12 Oct 2014 21:42:15 -0000\r
60 \r
61 The new outputs allow printing senders, recipients or both of matching\r
62 messages. The --output option is converted from "keyword" argument to\r
63 "flags" argument, which means that the user can use --output=sender and\r
64 --output=recipients simultaneously, to print both. Other combinations\r
65 produce an error.\r
66 \r
67 This code based on a patch from Jani Nikula.\r
68 ---\r
69  completion/notmuch-completion.bash |   2 +-\r
70  completion/notmuch-completion.zsh  |   3 +-\r
71  doc/man1/notmuch-search.rst        |  22 +++++++-\r
72  notmuch-search.c                   | 110 ++++++++++++++++++++++++++++++++++---\r
73  test/T090-search-output.sh         |  64 +++++++++++++++++++++\r
74  5 files changed, 189 insertions(+), 12 deletions(-)\r
75 \r
76 diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash\r
77 index 0571dc9..cfbd389 100644\r
78 --- a/completion/notmuch-completion.bash\r
79 +++ b/completion/notmuch-completion.bash\r
80 @@ -294,7 +294,7 @@ _notmuch_search()\r
81             return\r
82             ;;\r
83         --output)\r
84 -           COMPREPLY=( $( compgen -W "summary threads messages files tags" -- "${cur}" ) )\r
85 +           COMPREPLY=( $( compgen -W "summary threads messages files tags sender recipients" -- "${cur}" ) )\r
86             return\r
87             ;;\r
88         --sort)\r
89 diff --git a/completion/notmuch-completion.zsh b/completion/notmuch-completion.zsh\r
90 index 67a9aba..3e52a00 100644\r
91 --- a/completion/notmuch-completion.zsh\r
92 +++ b/completion/notmuch-completion.zsh\r
93 @@ -52,7 +52,8 @@ _notmuch_search()\r
94    _arguments -s : \\r
95      '--max-threads=[display only the first x threads from the search results]:number of threads to show: ' \\r
96      '--first=[omit the first x threads from the search results]:number of threads to omit: ' \\r
97 -    '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))'\r
98 +    '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \\r
99 +    '--output=[select what to output]:output:((summary threads messages files tags sender recipients))'\r
100  }\r
101  \r
102  _notmuch()\r
103 diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst\r
104 index 90160f2..c9d38b1 100644\r
105 --- a/doc/man1/notmuch-search.rst\r
106 +++ b/doc/man1/notmuch-search.rst\r
107 @@ -35,7 +35,7 @@ Supported options for **search** include\r
108          intended for programs that invoke **notmuch(1)** internally. If\r
109          omitted, the latest supported version will be used.\r
110  \r
111 -    ``--output=(summary|threads|messages|files|tags)``\r
112 +    ``--output=(summary|threads|messages|files|tags|sender|recipients)``\r
113  \r
114          **summary**\r
115              Output a summary of each thread with any message matching\r
116 @@ -78,6 +78,26 @@ Supported options for **search** include\r
117              by null characters (--format=text0), as a JSON array\r
118              (--format=json), or as an S-Expression list (--format=sexp).\r
119  \r
120 +       **sender**\r
121 +            Output all addresses from the *From* header that appear on\r
122 +            any message matching the search terms, either one per line\r
123 +            (--format=text), separated by null characters\r
124 +            (--format=text0), as a JSON array (--format=json), or as\r
125 +            an S-Expression list (--format=sexp).\r
126 +\r
127 +           Note: Searching for **sender** should be much faster than\r
128 +           searching for **recipients**, because sender addresses are\r
129 +           cached directly in the database whereas other addresses\r
130 +           need to be fetched from message files.\r
131 +\r
132 +       **recipients**\r
133 +            Like **sender** but for addresses from *To*, *Cc* and\r
134 +           *Bcc* headers.\r
135 +\r
136 +       This option can be given multiple times to combine different\r
137 +       outputs. Curently, this is only supported for **sender** and\r
138 +       **recipients** outputs.\r
139 +\r
140      ``--sort=``\ (**newest-first**\ \|\ **oldest-first**)\r
141          This option can be used to present results in either\r
142          chronological order (**oldest-first**) or reverse chronological\r
143 diff --git a/notmuch-search.c b/notmuch-search.c\r
144 index 5ac2a26..74588f8 100644\r
145 --- a/notmuch-search.c\r
146 +++ b/notmuch-search.c\r
147 @@ -23,11 +23,14 @@\r
148  #include "string-util.h"\r
149  \r
150  typedef enum {\r
151 -    OUTPUT_SUMMARY,\r
152 -    OUTPUT_THREADS,\r
153 -    OUTPUT_MESSAGES,\r
154 -    OUTPUT_FILES,\r
155 -    OUTPUT_TAGS\r
156 +    OUTPUT_SUMMARY     = 1 << 0,\r
157 +    OUTPUT_THREADS     = 1 << 1,\r
158 +    OUTPUT_MESSAGES    = 1 << 2,\r
159 +    OUTPUT_FILES       = 1 << 3,\r
160 +    OUTPUT_TAGS                = 1 << 4,\r
161 +    OUTPUT_SENDER      = 1 << 5,\r
162 +    OUTPUT_RECIPIENTS  = 1 << 6,\r
163 +    OUTPUT_ADDRESSES   = OUTPUT_SENDER | OUTPUT_RECIPIENTS,\r
164  } output_t;\r
165  \r
166  typedef struct {\r
167 @@ -220,6 +223,67 @@ do_search_threads (search_options_t *o)\r
168      return 0;\r
169  }\r
170  \r
171 +static void\r
172 +print_address_list (const search_options_t *o, InternetAddressList *list)\r
173 +{\r
174 +    InternetAddress *address;\r
175 +    int i;\r
176 +\r
177 +    for (i = 0; i < internet_address_list_length (list); i++) {\r
178 +       address = internet_address_list_get_address (list, i);\r
179 +       if (INTERNET_ADDRESS_IS_GROUP (address)) {\r
180 +           InternetAddressGroup *group;\r
181 +           InternetAddressList *group_list;\r
182 +\r
183 +           group = INTERNET_ADDRESS_GROUP (address);\r
184 +           group_list = internet_address_group_get_members (group);\r
185 +           if (group_list == NULL)\r
186 +               continue;\r
187 +\r
188 +           print_address_list (o, group_list);\r
189 +       } else {\r
190 +           InternetAddressMailbox *mailbox;\r
191 +           const char *name;\r
192 +           const char *addr;\r
193 +           char *full_address;\r
194 +\r
195 +           mailbox = INTERNET_ADDRESS_MAILBOX (address);\r
196 +\r
197 +           name = internet_address_get_name (address);\r
198 +           addr = internet_address_mailbox_get_addr (mailbox);\r
199 +\r
200 +           if (name && *name)\r
201 +               full_address = talloc_asprintf (o->format, "%s <%s>", name, addr);\r
202 +           else\r
203 +               full_address = talloc_strdup (o->format, addr);\r
204 +\r
205 +           if (!full_address) {\r
206 +               fprintf (stderr, "Error: out of memory\n");\r
207 +               break;\r
208 +           }\r
209 +           o->format->string (o->format, full_address);\r
210 +           o->format->separator (o->format);\r
211 +\r
212 +           talloc_free (full_address);\r
213 +       }\r
214 +    }\r
215 +}\r
216 +\r
217 +static void\r
218 +print_address_string (const search_options_t *o, const char *recipients)\r
219 +{\r
220 +    InternetAddressList *list;\r
221 +\r
222 +    if (recipients == NULL)\r
223 +       return;\r
224 +\r
225 +    list = internet_address_list_parse_string (recipients);\r
226 +    if (list == NULL)\r
227 +       return;\r
228 +\r
229 +    print_address_list (o, list);\r
230 +}\r
231 +\r
232  static int\r
233  do_search_messages (search_options_t *o)\r
234  {\r
235 @@ -266,11 +330,29 @@ do_search_messages (search_options_t *o)\r
236             \r
237             notmuch_filenames_destroy( filenames );\r
238  \r
239 -       } else { /* output == OUTPUT_MESSAGES */\r
240 +       } else if (o->output == OUTPUT_MESSAGES) {\r
241             format->set_prefix (format, "id");\r
242             format->string (format,\r
243                             notmuch_message_get_message_id (message));\r
244             format->separator (format);\r
245 +       } else {\r
246 +           if (o->output & OUTPUT_SENDER) {\r
247 +               const char *addrs;\r
248 +\r
249 +               addrs = notmuch_message_get_header (message, "from");\r
250 +               print_address_string (o, addrs);\r
251 +           }\r
252 +\r
253 +           if (o->output & OUTPUT_RECIPIENTS) {\r
254 +               const char *hdrs[] = { "to", "cc", "bcc" };\r
255 +               const char *addrs;\r
256 +               size_t j;\r
257 +\r
258 +               for (j = 0; j < ARRAY_SIZE (hdrs); j++) {\r
259 +                   addrs = notmuch_message_get_header (message, hdrs[j]);\r
260 +                   print_address_string (o, addrs);\r
261 +               }\r
262 +           }\r
263         }\r
264  \r
265         notmuch_message_destroy (message);\r
266 @@ -337,7 +419,7 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])\r
267      notmuch_database_t *notmuch;\r
268      search_options_t o = {\r
269         .sort = NOTMUCH_SORT_NEWEST_FIRST,\r
270 -       .output = OUTPUT_SUMMARY,\r
271 +       .output = 0,\r
272         .offset = 0,\r
273         .limit = -1, /* unlimited */\r
274         .dupe = -1,\r
275 @@ -366,10 +448,12 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])\r
276                                   { "text0", NOTMUCH_FORMAT_TEXT0 },\r
277                                   { 0, 0 } } },\r
278         { NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 },\r
279 -       { NOTMUCH_OPT_KEYWORD, &o.output, "output", 'o',\r
280 +       { NOTMUCH_OPT_KEYWORD_FLAGS, &o.output, "output", 'o',\r
281           (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },\r
282                                   { "threads", OUTPUT_THREADS },\r
283                                   { "messages", OUTPUT_MESSAGES },\r
284 +                                 { "sender", OUTPUT_SENDER },\r
285 +                                 { "recipients", OUTPUT_RECIPIENTS },\r
286                                   { "files", OUTPUT_FILES },\r
287                                   { "tags", OUTPUT_TAGS },\r
288                                   { 0, 0 } } },\r
289 @@ -389,6 +473,9 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])\r
290      if (opt_index < 0)\r
291         return EXIT_FAILURE;\r
292  \r
293 +    if (! o.output)\r
294 +       o.output = OUTPUT_SUMMARY;\r
295 +\r
296      switch (format_sel) {\r
297      case NOTMUCH_FORMAT_TEXT:\r
298         o.format = sprinter_text_create (config, stdout);\r
299 @@ -455,18 +542,23 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])\r
300      }\r
301  \r
302      switch (o.output) {\r
303 -    default:\r
304      case OUTPUT_SUMMARY:\r
305      case OUTPUT_THREADS:\r
306         ret = do_search_threads (&o);\r
307         break;\r
308      case OUTPUT_MESSAGES:\r
309 +    case OUTPUT_SENDER:\r
310 +    case OUTPUT_RECIPIENTS:\r
311 +    case OUTPUT_ADDRESSES:\r
312      case OUTPUT_FILES:\r
313         ret = do_search_messages (&o);\r
314         break;\r
315      case OUTPUT_TAGS:\r
316         ret = do_search_tags (notmuch, o.format, o.query);\r
317         break;\r
318 +    default:\r
319 +       fprintf (stderr, "Error: the combination of outputs is not supported.\n");\r
320 +       ret = 1;\r
321      }\r
322  \r
323      notmuch_query_destroy (o.query);\r
324 diff --git a/test/T090-search-output.sh b/test/T090-search-output.sh\r
325 index 947d572..e696c01 100755\r
326 --- a/test/T090-search-output.sh\r
327 +++ b/test/T090-search-output.sh\r
328 @@ -387,6 +387,70 @@ cat <<EOF >EXPECTED\r
329  EOF\r
330  test_expect_equal_file OUTPUT EXPECTED\r
331  \r
332 +test_begin_subtest "--output=sender"\r
333 +notmuch search --output=sender '*' | sort | uniq --count >OUTPUT\r
334 +cat <<EOF >EXPECTED\r
335 +      1 Adrian Perez de Castro <aperez@igalia.com>\r
336 +      2 Alex Botero-Lowry <alex.boterolowry@gmail.com>\r
337 +      4 Alexander Botero-Lowry <alex.boterolowry@gmail.com>\r
338 +      1 Aron Griffis <agriffis@n01se.net>\r
339 +     12 Carl Worth <cworth@cworth.org>\r
340 +      1 Chris Wilson <chris@chris-wilson.co.uk>\r
341 +      1 François Boulogne <boulogne.f@gmail.com>\r
342 +      1 Ingmar Vanhassel <ingmar@exherbo.org>\r
343 +      1 Israel Herraiz <isra@herraiz.org>\r
344 +      4 Jan Janak <jan@ryngle.com>\r
345 +      2 Jjgod Jiang <gzjjgod@gmail.com>\r
346 +      7 Keith Packard <keithp@keithp.com>\r
347 +      5 Lars Kellogg-Stedman <lars@seas.harvard.edu>\r
348 +      5 Mikhail Gusarov <dottedmag@dottedmag.net>\r
349 +      1 Olivier Berger <olivier.berger@it-sudparis.eu>\r
350 +      1 Rolland Santimano <rollandsantimano@yahoo.com>\r
351 +      3 Stewart Smith <stewart@flamingspork.com>\r
352 +EOF\r
353 +test_expect_equal_file OUTPUT EXPECTED\r
354 +\r
355 +test_begin_subtest "--output=recipients"\r
356 +notmuch search --output=recipients '*' | sort | uniq --count >OUTPUT\r
357 +cat <<EOF >EXPECTED\r
358 +      1 Allan McRae <allan@archlinux.org>\r
359 +      1 Discussion about the Arch User Repository (AUR) <aur-general@archlinux.org>\r
360 +      1 Keith Packard <keithp@keithp.com>\r
361 +      1 Mikhail Gusarov <dottedmag@dottedmag.net>\r
362 +      2 notmuch <notmuch@notmuchmail.org>\r
363 +     48 notmuch@notmuchmail.org\r
364 +      1 olivier.berger@it-sudparis.eu\r
365 +EOF\r
366 +test_expect_equal_file OUTPUT EXPECTED\r
367 +\r
368 +test_begin_subtest "--output=sender --output=recipients"\r
369 +notmuch search --output=sender --output=recipients '*' | sort | uniq --count >OUTPUT\r
370 +cat <<EOF >EXPECTED\r
371 +      1 Adrian Perez de Castro <aperez@igalia.com>\r
372 +      2 Alex Botero-Lowry <alex.boterolowry@gmail.com>\r
373 +      4 Alexander Botero-Lowry <alex.boterolowry@gmail.com>\r
374 +      1 Allan McRae <allan@archlinux.org>\r
375 +      1 Aron Griffis <agriffis@n01se.net>\r
376 +     12 Carl Worth <cworth@cworth.org>\r
377 +      1 Chris Wilson <chris@chris-wilson.co.uk>\r
378 +      1 Discussion about the Arch User Repository (AUR) <aur-general@archlinux.org>\r
379 +      1 François Boulogne <boulogne.f@gmail.com>\r
380 +      1 Ingmar Vanhassel <ingmar@exherbo.org>\r
381 +      1 Israel Herraiz <isra@herraiz.org>\r
382 +      4 Jan Janak <jan@ryngle.com>\r
383 +      2 Jjgod Jiang <gzjjgod@gmail.com>\r
384 +      8 Keith Packard <keithp@keithp.com>\r
385 +      5 Lars Kellogg-Stedman <lars@seas.harvard.edu>\r
386 +      6 Mikhail Gusarov <dottedmag@dottedmag.net>\r
387 +      1 Olivier Berger <olivier.berger@it-sudparis.eu>\r
388 +      1 Rolland Santimano <rollandsantimano@yahoo.com>\r
389 +      3 Stewart Smith <stewart@flamingspork.com>\r
390 +      2 notmuch <notmuch@notmuchmail.org>\r
391 +     48 notmuch@notmuchmail.org\r
392 +      1 olivier.berger@it-sudparis.eu\r
393 +EOF\r
394 +test_expect_equal_file OUTPUT EXPECTED\r
395 +\r
396  test_begin_subtest "sanitize output for quoted-printable line-breaks in author and subject"\r
397  add_message "[subject]='two =?ISO-8859-1?Q?line=0A_subject?=\r
398         headers'"\r
399 -- \r
400 2.1.1\r
401 \r