[PATCH 08/11] cli: add thread recipients to search output
[notmuch-archives.git] / 94 / 1a12cbfa527b0041eb8d1a33947a7ea68b8bcd
1 Return-Path: <amdragon@mit.edu>\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 1CD36431FAF\r
6         for <notmuch@notmuchmail.org>; Sun, 22 Jul 2012 09:08:50 -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: -0.7\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
12         tests=[RCVD_IN_DNSWL_LOW=-0.7] 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 XINKmzhsxIti for <notmuch@notmuchmail.org>;\r
16         Sun, 22 Jul 2012 09:08:49 -0700 (PDT)\r
17 Received: from dmz-mailsec-scanner-4.mit.edu (DMZ-MAILSEC-SCANNER-4.MIT.EDU\r
18         [18.9.25.15])\r
19         by olra.theworths.org (Postfix) with ESMTP id D2AA5431FAE\r
20         for <notmuch@notmuchmail.org>; Sun, 22 Jul 2012 09:08:48 -0700 (PDT)\r
21 X-AuditID: 1209190f-b7f306d0000008b4-b8-500c258f283e\r
22 Received: from mailhub-auth-1.mit.edu ( [18.9.21.35])\r
23         by dmz-mailsec-scanner-4.mit.edu (Symantec Messaging Gateway) with SMTP\r
24         id B3.D5.02228.F852C005; Sun, 22 Jul 2012 12:08:47 -0400 (EDT)\r
25 Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
26         by mailhub-auth-1.mit.edu (8.13.8/8.9.2) with ESMTP id q6MG8lvG010878; \r
27         Sun, 22 Jul 2012 12:08:47 -0400\r
28 Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
29         (authenticated bits=0)\r
30         (User authenticated as amdragon@ATHENA.MIT.EDU)\r
31         by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id q6MG8iZl004254\r
32         (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
33         Sun, 22 Jul 2012 12:08:46 -0400 (EDT)\r
34 Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.77)\r
35         (envelope-from <amdragon@mit.edu>)\r
36         id 1Ssyi4-0007E5-EH; Sun, 22 Jul 2012 12:08:44 -0400\r
37 Date: Sun, 22 Jul 2012 12:08:43 -0400\r
38 From: Austin Clements <amdragon@MIT.EDU>\r
39 To: craven@gmx.net\r
40 Subject: Re: [PATCH v7 2/3] Add structured output formatter for JSON and\r
41         plain text (but don't use them yet).\r
42 Message-ID: <20120722160843.GC31834@mit.edu>\r
43 References: <20120718194819.GP31670@mit.edu>\r
44         <1342766173-1344-1-git-send-email-craven@gmx.net>\r
45         <1342766173-1344-3-git-send-email-craven@gmx.net>\r
46 MIME-Version: 1.0\r
47 Content-Type: text/plain; charset=us-ascii\r
48 Content-Disposition: inline\r
49 In-Reply-To: <1342766173-1344-3-git-send-email-craven@gmx.net>\r
50 User-Agent: Mutt/1.5.21 (2010-09-15)\r
51 X-Brightmail-Tracker:\r
52  H4sIAAAAAAAAA+NgFmpmleLIzCtJLcpLzFFi42IR4hRV1u1X5QkwOPPN0mJvQzujxfWbM5kd\r
53         mDwWb9rP5vFs1S3mAKYoLpuU1JzMstQifbsEroybBx8xFewIrtjy4ANzA+M3uy5GDg4JAROJ\r
54         PzuKuxg5gUwxiQv31rOB2EIC+xgljjxK7GLkArI3MErs6DnPDOGcZJK4vesflLOEUeLhkudg\r
55         LSwCqhIf+jsYQWw2AQ2JbfuXg9kiAkISk768YgGxmQWkJb79bmYCsYUF8iVWruhlBbF5BXQk\r
56         jj15wQYxdAqjROupVkaIhKDEyZlPoJq1JG78e8kEcjbIoOX/OEDCnAJ2Ek83PGYHsUUFVCSm\r
57         nNzGNoFRaBaS7llIumchdC9gZF7FKJuSW6Wbm5iZU5yarFucnJiXl1qka6KXm1mil5pSuokR\r
58         HNaS/DsYvx1UOsQowMGoxMNr6M4dIMSaWFZcmXuIUZKDSUmUd4U0T4AQX1J+SmVGYnFGfFFp\r
59         TmrxIUYJDmYlEd7L14HKeVMSK6tSi/JhUtIcLErivFdTbvoLCaQnlqRmp6YWpBbBZGU4OJQk\r
60         eG+oAA0VLEpNT61Iy8wpQUgzcXCCDOcBGr4BpIa3uCAxtzgzHSJ/ilFRSpx3BkhCACSRUZoH\r
61         1wtLO68YxYFeEebdCVLFA0xZcN2vgAYzAQ2WzuICGVySiJCSamBkSH1aeEfxUIQOe0BAtNFh\r
62         loSOwkecu4U1nJjvi70QfJ742tFVUv0Rq+MnlVWNR51PXN7Vtu7kLul7k4+Uzl2wuLN/6o38\r
63         CKNDnSKMawKztglu3jjLV8/W/qF+VsDjxF+KZ2oVzv3JSP8k+dP2W67kCjYDU/fN/8rVZUOL\r
64         /xn3f7boex/73UqJpTgj0VCLuag4EQDaJVlnFgMAAA==\r
65 Cc: notmuch@notmuchmail.org\r
66 X-BeenThere: notmuch@notmuchmail.org\r
67 X-Mailman-Version: 2.1.13\r
68 Precedence: list\r
69 List-Id: "Use and development of the notmuch mail system."\r
70         <notmuch.notmuchmail.org>\r
71 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
72         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
73 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
74 List-Post: <mailto:notmuch@notmuchmail.org>\r
75 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
76 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
77         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
78 X-List-Received-Date: Sun, 22 Jul 2012 16:08:50 -0000\r
79 \r
80 Quoth craven@gmx.net on Jul 20 at  8:36 am:\r
81 > From: <craven@gmx.net>\r
82\r
83 > Using the new structured printer support in sprinter.h, implement\r
84 > sprinter_json_create, which returns a new JSON structured output\r
85 > formatter. The formatter prints output similar to the existing JSON, but\r
86 > with differences in whitespace (mostly newlines, --output=summary prints\r
87 > the entire message summary on one line, not split across multiple lines).\r
88\r
89 > Also implement a "structured" formatter for plain text that prints\r
90 > prefixed strings, to be used with notmuch-search.c plain text output.\r
91 > ---\r
92 >  Makefile.local  |   2 +\r
93 >  sprinter-json.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
94 >  sprinter-text.c | 126 ++++++++++++++++++++++++++++++++++++++\r
95 >  sprinter.h      |  10 +++\r
96 >  4 files changed, 323 insertions(+)\r
97 >  create mode 100644 sprinter-json.c\r
98 >  create mode 100644 sprinter-text.c\r
99\r
100 > diff --git a/Makefile.local b/Makefile.local\r
101 > index a890df2..296995d 100644\r
102 > --- a/Makefile.local\r
103 > +++ b/Makefile.local\r
104 > @@ -290,6 +290,8 @@ notmuch_client_srcs =             \\r
105 >       notmuch-show.c          \\r
106 >       notmuch-tag.c           \\r
107 >       notmuch-time.c          \\r
108 > +     sprinter-json.c         \\r
109 > +     sprinter-text.c         \\r
110 >       query-string.c          \\r
111 >       mime-node.c             \\r
112 >       crypto.c                \\r
113 > diff --git a/sprinter-json.c b/sprinter-json.c\r
114 > new file mode 100644\r
115 > index 0000000..32daa5a\r
116 > --- /dev/null\r
117 > +++ b/sprinter-json.c\r
118 > @@ -0,0 +1,185 @@\r
119 > +#include <stdbool.h>\r
120 > +#include <stdio.h>\r
121 > +#include <talloc.h>\r
122 > +#include "sprinter.h"\r
123 > +\r
124 > +struct sprinter_json {\r
125 > +    struct sprinter vtable;\r
126 > +    FILE *stream;\r
127 > +    /* Top of the state stack, or NULL if the printer is not currently\r
128 > +     * inside any aggregate types. */\r
129 > +    struct json_state *state;\r
130 > +\r
131 > +    /* A flag to signify that a separator should be inserted in the\r
132 > +     * output as soon as possible.\r
133 > +     */\r
134 > +    notmuch_bool_t insert_separator;\r
135 > +};\r
136 > +\r
137 > +struct json_state {\r
138 > +    struct json_state *parent;\r
139 > +    /* True if nothing has been printed in this aggregate yet.\r
140 > +     * Suppresses the comma before a value. */\r
141 > +    notmuch_bool_t first;\r
142 > +    /* The character that closes the current aggregate. */\r
143 > +    char close;\r
144 > +};\r
145 > +\r
146 > +/* Helper function to set up the stream to print a value.  If this\r
147 > + * value follows another value, prints a comma. */\r
148 > +static struct sprinter_json *\r
149 > +json_begin_value (struct sprinter *sp)\r
150 > +{\r
151 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
152 > +\r
153 > +    if (spj->state) {\r
154 > +     if (! spj->state->first) {\r
155 > +         fputc (',', spj->stream);\r
156 > +         if (spj->insert_separator) {\r
157 > +             fputc ('\n', spj->stream);\r
158 > +             spj->insert_separator = FALSE;\r
159 > +         } else\r
160 > +             fputc (' ', spj->stream);\r
161 > +     } else\r
162 > +         spj->state->first = FALSE;\r
163 \r
164 Just a nit: if you roll a v8, it would be nice to have braces around\r
165 both of these else parts to match the braces on the then parts.\r
166 Obviously this doesn't warrant a v8 on its own, though.\r
167 \r
168 > +    }\r
169 > +    return spj;\r
170 > +}\r
171 > +\r
172 > +/* Helper function to begin an aggregate type.  Prints the open\r
173 > + * character and pushes a new state frame. */\r
174 > +static void\r
175 > +json_begin_aggregate (struct sprinter *sp, char open, char close)\r
176 > +{\r
177 > +    struct sprinter_json *spj = json_begin_value (sp);\r
178 > +    struct json_state *state = talloc (spj, struct json_state);\r
179 > +\r
180 > +    fputc (open, spj->stream);\r
181 > +    state->parent = spj->state;\r
182 > +    state->first = TRUE;\r
183 > +    state->close = close;\r
184 > +    spj->state = state;\r
185 > +}\r
186 > +\r
187 > +static void\r
188 > +json_begin_map (struct sprinter *sp)\r
189 > +{\r
190 > +    json_begin_aggregate (sp, '{', '}');\r
191 > +}\r
192 > +\r
193 > +static void\r
194 > +json_begin_list (struct sprinter *sp)\r
195 > +{\r
196 > +    json_begin_aggregate (sp, '[', ']');\r
197 > +}\r
198 > +\r
199 > +static void\r
200 > +json_end (struct sprinter *sp)\r
201 > +{\r
202 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
203 > +    struct json_state *state = spj->state;\r
204 > +\r
205 > +    fputc (spj->state->close, spj->stream);\r
206 > +    spj->state = state->parent;\r
207 > +    talloc_free (state);\r
208 > +    if (spj->state == NULL)\r
209 > +     fputc ('\n', spj->stream);\r
210 > +}\r
211 > +\r
212 > +static void\r
213 > +json_string (struct sprinter *sp, const char *val)\r
214 > +{\r
215 > +    static const char *const escapes[] = {\r
216 > +     ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",\r
217 > +     ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"\r
218 > +    };\r
219 > +    struct sprinter_json *spj = json_begin_value (sp);\r
220 > +\r
221 > +    fputc ('"', spj->stream);\r
222 > +    for (; *val; ++val) {\r
223 > +     unsigned char ch = *val;\r
224 > +     if (ch < ARRAY_SIZE (escapes) && escapes[ch])\r
225 > +         fputs (escapes[ch], spj->stream);\r
226 > +     else if (ch >= 32)\r
227 > +         fputc (ch, spj->stream);\r
228 > +     else\r
229 > +         fprintf (spj->stream, "\\u%04x", ch);\r
230 > +    }\r
231 > +    fputc ('"', spj->stream);\r
232 > +}\r
233 > +\r
234 > +static void\r
235 > +json_integer (struct sprinter *sp, int val)\r
236 > +{\r
237 > +    struct sprinter_json *spj = json_begin_value (sp);\r
238 > +\r
239 > +    fprintf (spj->stream, "%d", val);\r
240 > +}\r
241 > +\r
242 > +static void\r
243 > +json_boolean (struct sprinter *sp, notmuch_bool_t val)\r
244 > +{\r
245 > +    struct sprinter_json *spj = json_begin_value (sp);\r
246 > +\r
247 > +    fputs (val ? "true" : "false", spj->stream);\r
248 > +}\r
249 > +\r
250 > +static void\r
251 > +json_null (struct sprinter *sp)\r
252 > +{\r
253 > +    struct sprinter_json *spj = json_begin_value (sp);\r
254 > +\r
255 > +    fputs ("null", spj->stream);\r
256 > +}\r
257 > +\r
258 > +static void\r
259 > +json_map_key (struct sprinter *sp, const char *key)\r
260 > +{\r
261 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
262 > +\r
263 > +    json_string (sp, key);\r
264 > +    fputs (": ", spj->stream);\r
265 > +    spj->state->first = TRUE;\r
266 > +}\r
267 > +\r
268 > +static void\r
269 > +json_set_prefix (unused (struct sprinter *sp), unused (const char *name))\r
270 > +{\r
271 > +}\r
272 > +\r
273 > +static void\r
274 > +json_separator (struct sprinter *sp)\r
275 > +{\r
276 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
277 > +\r
278 > +    spj->insert_separator = TRUE;\r
279 > +}\r
280 > +\r
281 > +struct sprinter *\r
282 > +sprinter_json_create (const void *ctx, FILE *stream)\r
283 > +{\r
284 > +    static const struct sprinter_json template = {\r
285 > +     .vtable = {\r
286 > +         .begin_map = json_begin_map,\r
287 > +         .begin_list = json_begin_list,\r
288 > +         .end = json_end,\r
289 > +         .string = json_string,\r
290 > +         .integer = json_integer,\r
291 > +         .boolean = json_boolean,\r
292 > +         .null = json_null,\r
293 > +         .map_key = json_map_key,\r
294 > +         .separator = json_separator,\r
295 > +         .set_prefix = json_set_prefix,\r
296 > +         .is_text_printer = FALSE,\r
297 > +     }\r
298 > +    };\r
299 > +    struct sprinter_json *res;\r
300 > +\r
301 > +    res = talloc (ctx, struct sprinter_json);\r
302 > +    if (! res)\r
303 > +     return NULL;\r
304 > +\r
305 > +    *res = template;\r
306 > +    res->stream = stream;\r
307 > +    return &res->vtable;\r
308 > +}\r
309 > diff --git a/sprinter-text.c b/sprinter-text.c\r
310 > new file mode 100644\r
311 > index 0000000..b208840\r
312 > --- /dev/null\r
313 > +++ b/sprinter-text.c\r
314 > @@ -0,0 +1,126 @@\r
315 > +#include <stdbool.h>\r
316 > +#include <stdio.h>\r
317 > +#include <talloc.h>\r
318 > +#include "sprinter.h"\r
319 > +\r
320 > +/* "Structured printer" interface for unstructured text printing.\r
321 > + * Note that --output=summary is dispatched and formatted in\r
322 > + * notmuch-search.c, the code in this file is only used for all other\r
323 > + * output types.\r
324 > + */\r
325 > +\r
326 > +struct sprinter_text {\r
327 > +    struct sprinter vtable;\r
328 > +    FILE *stream;\r
329 > +\r
330 > +    /* The current prefix to be printed with string/integer/boolean\r
331 > +     * data.\r
332 > +     */\r
333 > +    const char *current_prefix;\r
334 > +\r
335 > +    /* A flag to indicate if this is the first tag. Used in list of tags\r
336 > +     * for summary.\r
337 > +     */\r
338 > +    notmuch_bool_t first_tag;\r
339 > +};\r
340 > +\r
341 > +static void\r
342 > +text_string (struct sprinter *sp, const char *val)\r
343 > +{\r
344 > +    struct sprinter_text *sptxt = (struct sprinter_text *) sp;\r
345 > +\r
346 > +    if (sptxt->current_prefix != NULL)\r
347 > +     fprintf (sptxt->stream, "%s:", sptxt->current_prefix);\r
348 > +\r
349 > +    fputs(val, sptxt->stream);\r
350 > +}\r
351 > +\r
352 > +static void\r
353 > +text_integer (struct sprinter *sp, int val)\r
354 > +{\r
355 > +    struct sprinter_text *sptxt = (struct sprinter_text *) sp;\r
356 > +\r
357 > +    fprintf (sptxt->stream, "%d", val);\r
358 > +}\r
359 > +\r
360 > +static void\r
361 > +text_boolean (struct sprinter *sp, notmuch_bool_t val)\r
362 > +{\r
363 > +    struct sprinter_text *sptxt = (struct sprinter_text *) sp;\r
364 > +\r
365 > +    fputs (val ? "true" : "false", sptxt->stream);\r
366 > +}\r
367 > +\r
368 > +static void\r
369 > +text_separator (struct sprinter *sp)\r
370 > +{\r
371 > +    struct sprinter_text *sptxt = (struct sprinter_text *) sp;\r
372 > +\r
373 > +    fputc ('\n', sptxt->stream);\r
374 > +}\r
375 > +\r
376 > +static void\r
377 > +text_set_prefix (struct sprinter *sp, const char *prefix)\r
378 > +{\r
379 > +    struct sprinter_text *sptxt = (struct sprinter_text *) sp;\r
380 > +\r
381 > +    sptxt->current_prefix = prefix;\r
382 > +}\r
383 > +\r
384 > +/* The structure functions begin_map, begin_list, end and map_key\r
385 > + * don't do anything in the text formatter.\r
386 > + */\r
387 > +\r
388 > +static void\r
389 > +text_begin_map (unused (struct sprinter *sp))\r
390 > +{\r
391 > +}\r
392 > +\r
393 > +static void\r
394 > +text_begin_list (unused (struct sprinter *sp))\r
395 > +{\r
396 > +}\r
397 > +\r
398 > +static void\r
399 > +text_end (unused (struct sprinter *sp))\r
400 > +{\r
401 > +}\r
402 > +\r
403 > +static void\r
404 > +text_null (unused (struct sprinter *sp))\r
405 > +{\r
406 > +}\r
407 > +\r
408 > +static void\r
409 > +text_map_key (unused (struct sprinter *sp), unused (const char *key))\r
410 > +{\r
411 > +}\r
412 > +\r
413 > +struct sprinter *\r
414 > +sprinter_text_create (const void *ctx, FILE *stream)\r
415 > +{\r
416 > +    static const struct sprinter_text template = {\r
417 > +     .vtable = {\r
418 > +         .begin_map = text_begin_map,\r
419 > +         .begin_list = text_begin_list,\r
420 > +         .end = text_end,\r
421 > +         .string = text_string,\r
422 > +         .integer = text_integer,\r
423 > +         .boolean = text_boolean,\r
424 > +         .null = text_null,\r
425 > +         .map_key = text_map_key,\r
426 > +         .separator = text_separator,\r
427 > +         .set_prefix = text_set_prefix,\r
428 > +         .is_text_printer = TRUE,\r
429 > +     },\r
430 > +    };\r
431 > +    struct sprinter_text *res;\r
432 > +\r
433 > +    res = talloc (ctx, struct sprinter_text);\r
434 > +    if (! res)\r
435 > +     return NULL;\r
436 > +\r
437 > +    *res = template;\r
438 > +    res->stream = stream;\r
439 > +    return &res->vtable;\r
440 > +}\r
441 > diff --git a/sprinter.h b/sprinter.h\r
442 > index 77dc26f..6680d41 100644\r
443 > --- a/sprinter.h\r
444 > +++ b/sprinter.h\r
445 > @@ -55,4 +55,14 @@ typedef struct sprinter {\r
446 >      notmuch_bool_t is_text_printer;\r
447 >  } sprinter_t;\r
448 >  \r
449 > +\r
450 > +/* Create a new unstructured printer that emits the default text format\r
451 > + * for "notmuch search". */\r
452 > +struct sprinter *\r
453 > +sprinter_text_create (const void *ctx, FILE *stream);\r
454 > +\r
455 > +/* Create a new structure printer that emits JSON. */\r
456 > +struct sprinter *\r
457 > +sprinter_json_create (const void *ctx, FILE *stream);\r
458 > +\r
459 >  #endif // NOTMUCH_SPRINTER_H\r