Re: Hi all
[notmuch-archives.git] / 69 / 33bb28be80b843d871eb988d6fec11a28195ff
1 Return-Path: <craven@gmx.net>\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 B4B78431FAE\r
6         for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 01:13:45 -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.001\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0.001 tagged_above=-999 required=5\r
12         tests=[FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001]\r
13         autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id QEqTQsT8bffN for <notmuch@notmuchmail.org>;\r
17         Fri, 13 Jul 2012 01:13:42 -0700 (PDT)\r
18 Received: from mailout-de.gmx.net (mailout-de.gmx.net [213.165.64.23])\r
19         by olra.theworths.org (Postfix) with SMTP id 33915431FC4\r
20         for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 01:13:41 -0700 (PDT)\r
21 Received: (qmail invoked by alias); 13 Jul 2012 08:13:38 -0000\r
22 Received: from cm56-137-203.liwest.at (EHLO nexork.localdomain)\r
23  [86.56.137.203]        by mail.gmx.net (mp019) with SMTP; 13 Jul 2012 10:13:38 +0200\r
24 X-Authenticated: #201305\r
25 X-Provags-ID: V01U2FsdGVkX1/1LBN6uRX16FYH/ks8KYjbl0uzKhUbcIT8wucqYT\r
26         RWNNvnA9tM0Dn7\r
27 Received: by nexork.localdomain (Postfix, from userid 1000)\r
28         id CCBFF4527803; Fri, 13 Jul 2012 10:11:42 +0200 (CEST)\r
29 From: Peter Feigl <craven@gmx.net>\r
30 To: notmuch@notmuchmail.org\r
31 Subject: [PATCH v5 2/3] Add structured output formatter for JSON and text.\r
32 Date: Fri, 13 Jul 2012 10:11:36 +0200\r
33 Message-Id: <1342167097-25012-3-git-send-email-craven@gmx.net>\r
34 X-Mailer: git-send-email 1.7.11.1\r
35 In-Reply-To: <1342167097-25012-1-git-send-email-craven@gmx.net>\r
36 References: <20120710191331.GE7332@mit.edu>\r
37         <1342167097-25012-1-git-send-email-craven@gmx.net>\r
38 X-Y-GMX-Trusted: 0\r
39 X-BeenThere: notmuch@notmuchmail.org\r
40 X-Mailman-Version: 2.1.13\r
41 Precedence: list\r
42 List-Id: "Use and development of the notmuch mail system."\r
43         <notmuch.notmuchmail.org>\r
44 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
45         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
46 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
47 List-Post: <mailto:notmuch@notmuchmail.org>\r
48 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
49 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
50         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
51 X-List-Received-Date: Fri, 13 Jul 2012 08:13:46 -0000\r
52 \r
53 From: <craven@gmx.net>\r
54 \r
55 Using the new structured printer support in sprinter.h, implement\r
56 sprinter_json_create, which returns a new JSON structured output\r
57 formatter. The formatter prints output similar to the existing JSON, but\r
58 with differences in whitespace (mostly newlines, --output=summary prints\r
59 the entire message summary on one line, not split across multiple lines).\r
60 \r
61 Also implement a "structured" formatter that prints the current plain\r
62 text format. This passes all tests, but the exact formatting is probably\r
63 specific to notmuch-search and cannot easily (if at all) be adapted to\r
64 be used across all of notmuch-{search,reply,show,...}.\r
65 ---\r
66  Makefile.local         |   2 +\r
67  sprinter-json.c        | 184 +++++++++++++++++++++++++++++++++++++++++++++++\r
68  sprinter-text-search.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++\r
69  sprinter.h             |   8 +++\r
70  4 files changed, 384 insertions(+)\r
71  create mode 100644 sprinter-json.c\r
72  create mode 100644 sprinter-text-search.c\r
73 \r
74 diff --git a/Makefile.local b/Makefile.local\r
75 index a890df2..b6c7e0c 100644\r
76 --- a/Makefile.local\r
77 +++ b/Makefile.local\r
78 @@ -290,6 +290,8 @@ notmuch_client_srcs =               \\r
79         notmuch-show.c          \\r
80         notmuch-tag.c           \\r
81         notmuch-time.c          \\r
82 +       sprinter-json.c         \\r
83 +       sprinter-text-search.c  \\r
84         query-string.c          \\r
85         mime-node.c             \\r
86         crypto.c                \\r
87 diff --git a/sprinter-json.c b/sprinter-json.c\r
88 new file mode 100644\r
89 index 0000000..215151d\r
90 --- /dev/null\r
91 +++ b/sprinter-json.c\r
92 @@ -0,0 +1,184 @@\r
93 +#include <stdbool.h>\r
94 +#include <stdio.h>\r
95 +#include <talloc.h>\r
96 +#include "sprinter.h"\r
97 +\r
98 +struct sprinter_json {\r
99 +    struct sprinter vtable;\r
100 +    FILE *stream;\r
101 +    /* Top of the state stack, or NULL if the printer is not currently\r
102 +     * inside any aggregate types. */\r
103 +    struct json_state *state;\r
104 +\r
105 +    /* A flag to signify that a separator should be inserted in the\r
106 +     * output as soon as possible.\r
107 +     */\r
108 +    notmuch_bool_t insert_separator;\r
109 +};\r
110 +\r
111 +struct json_state {\r
112 +    struct json_state *parent;\r
113 +    /* True if nothing has been printed in this aggregate yet.\r
114 +     * Suppresses the comma before a value. */\r
115 +    notmuch_bool_t first;\r
116 +    /* The character that closes the current aggregate. */\r
117 +    char close;\r
118 +};\r
119 +\r
120 +/* Helper function to set up the stream to print a value.  If this\r
121 + * value follows another value, prints a comma. */\r
122 +static struct sprinter_json *\r
123 +json_begin_value (struct sprinter *sp)\r
124 +{\r
125 +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
126 +\r
127 +    if (spj->state) {\r
128 +       if (! spj->state->first) {\r
129 +           fputc (',', spj->stream);\r
130 +           if (spj->insert_separator) {\r
131 +               fputc ('\n', spj->stream);\r
132 +               spj->insert_separator = FALSE;\r
133 +           } else\r
134 +               fputc (' ', spj->stream);\r
135 +       } else\r
136 +           spj->state->first = FALSE;\r
137 +    }\r
138 +    return spj;\r
139 +}\r
140 +\r
141 +/* Helper function to begin an aggregate type.  Prints the open\r
142 + * character and pushes a new state frame. */\r
143 +static void\r
144 +json_begin_aggregate (struct sprinter *sp, char open, char close)\r
145 +{\r
146 +    struct sprinter_json *spj = json_begin_value (sp);\r
147 +    struct json_state *state = talloc (spj, struct json_state);\r
148 +\r
149 +    fputc (open, spj->stream);\r
150 +    state->parent = spj->state;\r
151 +    state->first = TRUE;\r
152 +    state->close = close;\r
153 +    spj->state = state;\r
154 +}\r
155 +\r
156 +static void\r
157 +json_begin_map (struct sprinter *sp)\r
158 +{\r
159 +    json_begin_aggregate (sp, '{', '}');\r
160 +}\r
161 +\r
162 +static void\r
163 +json_begin_list (struct sprinter *sp)\r
164 +{\r
165 +    json_begin_aggregate (sp, '[', ']');\r
166 +}\r
167 +\r
168 +static void\r
169 +json_end (struct sprinter *sp)\r
170 +{\r
171 +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
172 +    struct json_state *state = spj->state;\r
173 +\r
174 +    fputc (spj->state->close, spj->stream);\r
175 +    spj->state = state->parent;\r
176 +    talloc_free (state);\r
177 +    if (spj->state == NULL)\r
178 +       fputc ('\n', spj->stream);\r
179 +}\r
180 +\r
181 +static void\r
182 +json_string (struct sprinter *sp, const char *val)\r
183 +{\r
184 +    static const char *const escapes[] = {\r
185 +       ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",\r
186 +       ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"\r
187 +    };\r
188 +    struct sprinter_json *spj = json_begin_value (sp);\r
189 +\r
190 +    fputc ('"', spj->stream);\r
191 +    for (; *val; ++val) {\r
192 +       unsigned char ch = *val;\r
193 +       if (ch < ARRAY_SIZE (escapes) && escapes[ch])\r
194 +           fputs (escapes[ch], spj->stream);\r
195 +       else if (ch >= 32)\r
196 +           fputc (ch, spj->stream);\r
197 +       else\r
198 +           fprintf (spj->stream, "\\u%04x", ch);\r
199 +    }\r
200 +    fputc ('"', spj->stream);\r
201 +}\r
202 +\r
203 +static void\r
204 +json_integer (struct sprinter *sp, int val)\r
205 +{\r
206 +    struct sprinter_json *spj = json_begin_value (sp);\r
207 +\r
208 +    fprintf (spj->stream, "%d", val);\r
209 +}\r
210 +\r
211 +static void\r
212 +json_boolean (struct sprinter *sp, notmuch_bool_t val)\r
213 +{\r
214 +    struct sprinter_json *spj = json_begin_value (sp);\r
215 +\r
216 +    fputs (val ? "true" : "false", spj->stream);\r
217 +}\r
218 +\r
219 +static void\r
220 +json_null (struct sprinter *sp)\r
221 +{\r
222 +    struct sprinter_json *spj = json_begin_value (sp);\r
223 +\r
224 +    fputs ("null", spj->stream);\r
225 +}\r
226 +\r
227 +static void\r
228 +json_map_key (struct sprinter *sp, const char *key)\r
229 +{\r
230 +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
231 +\r
232 +    json_string (sp, key);\r
233 +    fputs (": ", spj->stream);\r
234 +    spj->state->first = TRUE;\r
235 +}\r
236 +\r
237 +static void\r
238 +json_set_prefix (unused (struct sprinter *sp), unused (const char *name))\r
239 +{\r
240 +}\r
241 +\r
242 +static void\r
243 +json_separator (struct sprinter *sp)\r
244 +{\r
245 +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
246 +\r
247 +    spj->insert_separator = TRUE;\r
248 +}\r
249 +\r
250 +struct sprinter *\r
251 +sprinter_json_create (const void *ctx, FILE *stream)\r
252 +{\r
253 +    static const struct sprinter_json template = {\r
254 +       .vtable = {\r
255 +           .begin_map = json_begin_map,\r
256 +           .begin_list = json_begin_list,\r
257 +           .end = json_end,\r
258 +           .string = json_string,\r
259 +           .integer = json_integer,\r
260 +           .boolean = json_boolean,\r
261 +           .null = json_null,\r
262 +           .map_key = json_map_key,\r
263 +           .separator = json_separator,\r
264 +           .set_prefix = json_set_prefix,\r
265 +       }\r
266 +    };\r
267 +    struct sprinter_json *res;\r
268 +\r
269 +    res = talloc (ctx, struct sprinter_json);\r
270 +    if (! res)\r
271 +       return NULL;\r
272 +\r
273 +    *res = template;\r
274 +    res->stream = stream;\r
275 +    return &res->vtable;\r
276 +}\r
277 diff --git a/sprinter-text-search.c b/sprinter-text-search.c\r
278 new file mode 100644\r
279 index 0000000..95ed9cb\r
280 --- /dev/null\r
281 +++ b/sprinter-text-search.c\r
282 @@ -0,0 +1,190 @@\r
283 +#include <stdbool.h>\r
284 +#include <stdio.h>\r
285 +#include <talloc.h>\r
286 +#include "sprinter.h"\r
287 +\r
288 +/* "Structured printer" interface for unstructured text printing.\r
289 + * This is at best a misuse of the interface, but it simplifies the code\r
290 + * in notmuch-search.c considerably.\r
291 + */\r
292 +\r
293 +struct sprinter_text_search {\r
294 +    struct sprinter vtable;\r
295 +    FILE *stream;\r
296 +\r
297 +    /* The current name or prefix to be printed with string/integer/boolean\r
298 +     * data.\r
299 +     */\r
300 +    const char *current_name;\r
301 +\r
302 +    /* A flag to indicate if this is the first tag. Used in list of tags\r
303 +     * for summary.\r
304 +     */\r
305 +    notmuch_bool_t first_tag;\r
306 +};\r
307 +\r
308 +/* struct text_search_state { */\r
309 +/*     struct text_search_state *parent; */\r
310 +/* }; */\r
311 +\r
312 +static notmuch_bool_t\r
313 +current_context (struct sprinter_text_search *sptxt, const char *marker)\r
314 +{\r
315 +    return (sptxt->current_name != NULL\r
316 +           && ! strncmp (marker, sptxt->current_name, strlen (marker)));\r
317 +}\r
318 +\r
319 +static void\r
320 +print_sanitized_string (FILE *stream, const char *str)\r
321 +{\r
322 +    if (NULL == str)\r
323 +       return;\r
324 +\r
325 +    for (; *str; str++) {\r
326 +       if ((unsigned char) (*str) < 32)\r
327 +           fputc ('?', stream);\r
328 +       else\r
329 +           fputc (*str, stream);\r
330 +    }\r
331 +}\r
332 +\r
333 +static void\r
334 +text_search_begin_map (unused (struct sprinter *sp))\r
335 +{\r
336 +}\r
337 +\r
338 +static void\r
339 +text_search_begin_list (struct sprinter *sp)\r
340 +{\r
341 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
342 +\r
343 +    if (current_context (sptxt, "tags")) {\r
344 +       fputs (" (", sptxt->stream);\r
345 +       sptxt->first_tag = TRUE;\r
346 +    }\r
347 +}\r
348 +\r
349 +static void\r
350 +text_search_end (struct sprinter *sp)\r
351 +{\r
352 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
353 +\r
354 +    if (current_context (sptxt, "tags")) {\r
355 +       fputc (')', sptxt->stream);\r
356 +       sptxt->current_name = NULL;\r
357 +    }\r
358 +}\r
359 +\r
360 +static void\r
361 +text_search_string (struct sprinter *sp, const char *val)\r
362 +{\r
363 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
364 +\r
365 +    if (sptxt->current_name != NULL) {\r
366 +       if (current_context (sptxt, "thread"))\r
367 +           fprintf ( sptxt->stream, "thread:%s ", val);\r
368 +       else if (current_context (sptxt, "date_relative"))\r
369 +           fprintf ( sptxt->stream, "%12s ", val);\r
370 +       else if (current_context (sptxt, "authors")) {\r
371 +           print_sanitized_string (sptxt->stream, val);\r
372 +           fputs ("; ", sptxt->stream);\r
373 +       } else if (current_context (sptxt, "subject"))\r
374 +           print_sanitized_string (sptxt->stream, val);\r
375 +       else if (current_context (sptxt, "tags")) {\r
376 +           if (! sptxt->first_tag)\r
377 +               fputc (' ', sptxt->stream);\r
378 +           else\r
379 +               sptxt->first_tag = FALSE;\r
380 +\r
381 +           fputs (val, sptxt->stream);\r
382 +       } else {\r
383 +           fputs (sptxt->current_name, sptxt->stream);\r
384 +           fputc (':', sptxt->stream);\r
385 +           fputs (val, sptxt->stream);\r
386 +       }\r
387 +    } else {\r
388 +       fputs (val, sptxt->stream);\r
389 +    }\r
390 +}\r
391 +\r
392 +static void\r
393 +text_search_integer (struct sprinter *sp, int val)\r
394 +{\r
395 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
396 +\r
397 +    if (sptxt->current_name != NULL) {\r
398 +       if (current_context (sptxt, "matched"))\r
399 +           fprintf ( sptxt->stream, "[%d/", val);\r
400 +       else if (current_context (sptxt, "total"))\r
401 +           fprintf ( sptxt->stream, "%d] ", val);\r
402 +    } else\r
403 +       fprintf (sptxt->stream, "%d", val);\r
404 +}\r
405 +\r
406 +static void\r
407 +text_search_boolean (struct sprinter *sp, notmuch_bool_t val)\r
408 +{\r
409 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
410 +\r
411 +    fputs (val ? "true" : "false", sptxt->stream);\r
412 +}\r
413 +\r
414 +static void\r
415 +text_search_null (struct sprinter *sp)\r
416 +{\r
417 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
418 +\r
419 +    fputs ("null", sptxt->stream);\r
420 +}\r
421 +\r
422 +static void\r
423 +text_search_map_key (struct sprinter *sp, const char *key)\r
424 +{\r
425 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
426 +\r
427 +    sptxt->current_name = key;\r
428 +}\r
429 +\r
430 +static void\r
431 +text_search_separator (struct sprinter *sp)\r
432 +{\r
433 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
434 +\r
435 +    fputc ('\n', sptxt->stream);\r
436 +}\r
437 +\r
438 +static void\r
439 +text_search_set_prefix (struct sprinter *sp, const char *name)\r
440 +{\r
441 +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
442 +\r
443 +    sptxt->current_name = name;\r
444 +}\r
445 +\r
446 +struct sprinter *\r
447 +sprinter_text_search_create (const void *ctx, FILE *stream)\r
448 +{\r
449 +    static const struct sprinter_text_search template = {\r
450 +       .vtable = {\r
451 +           .begin_map = text_search_begin_map,\r
452 +           .begin_list = text_search_begin_list,\r
453 +           .end = text_search_end,\r
454 +           .string = text_search_string,\r
455 +           .integer = text_search_integer,\r
456 +           .boolean = text_search_boolean,\r
457 +           .null = text_search_null,\r
458 +           .map_key = text_search_map_key,\r
459 +           .separator = text_search_separator,\r
460 +           .set_prefix = text_search_set_prefix,\r
461 +       }\r
462 +    };\r
463 +    struct sprinter_text_search *res;\r
464 +\r
465 +    res = talloc (ctx, struct sprinter_text_search);\r
466 +    if (! res)\r
467 +       return NULL;\r
468 +\r
469 +    *res = template;\r
470 +    res->stream = stream;\r
471 +    return &res->vtable;\r
472 +}\r
473 diff --git a/sprinter.h b/sprinter.h\r
474 index c9cd6a6..4241d65 100644\r
475 --- a/sprinter.h\r
476 +++ b/sprinter.h\r
477 @@ -47,4 +47,12 @@ typedef struct sprinter {\r
478      void (*separator)(struct sprinter *);\r
479  } sprinter_t;\r
480  \r
481 +/* Create a new unstructured printer that emits the default Text format for search. */\r
482 +struct sprinter *\r
483 +sprinter_text_search_create (const void *ctx, FILE *stream);\r
484 +\r
485 +/* Create a new structure printer that emits JSON. */\r
486 +struct sprinter *\r
487 +sprinter_json_create (const void *ctx, FILE *stream);\r
488 +\r
489  #endif // NOTMUCH_SPRINTER_H\r
490 -- \r
491 1.7.11.1\r
492 \r