Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / 7c / 4e2fccc640adbf2aad669245adf31eed1019ea
1 Return-Path: <peter.feigl@gmx.at>\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 3F10A431E64\r
6         for <notmuch@notmuchmail.org>; Tue, 10 Jul 2012 06:31:00 -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 bw+brtGDH3J0 for <notmuch@notmuchmail.org>;\r
17         Tue, 10 Jul 2012 06:30:58 -0700 (PDT)\r
18 Received: from mailout-de.gmx.net (mailout-de.gmx.net [213.165.64.22])\r
19         by olra.theworths.org (Postfix) with SMTP id 19B5A431FBF\r
20         for <notmuch@notmuchmail.org>; Tue, 10 Jul 2012 06:30:57 -0700 (PDT)\r
21 Received: (qmail invoked by alias); 10 Jul 2012 13:30:54 -0000\r
22 Received: from www.nexoid.at (EHLO mail.nexoid.at) [178.79.130.240]\r
23         by mail.gmx.net (mp036) with SMTP; 10 Jul 2012 15:30:54 +0200\r
24 X-Authenticated: #4563876\r
25 X-Provags-ID: V01U2FsdGVkX1/XoTBGRVrXIc58VjihNhIDcl3OPdOBMAm60udpav\r
26         64j9t/lDqlzk3M\r
27 Received: from nexoid (localhost [127.0.0.1])\r
28         by mail.nexoid.at (Postfix) with ESMTP id 4DFF1E00C\r
29         for <notmuch@notmuchmail.org>; Tue, 10 Jul 2012 15:30:51 +0200 (CEST)\r
30 From: <craven@gmx.net>\r
31 To: notmuch@notmuchmail.org\r
32 Subject: [PATCH] v2: Added better support for multiple structured output\r
33         formats.\r
34 In-Reply-To: <87liir944f.fsf@qmul.ac.uk>\r
35 References: <87liisue70.fsf@nexoid.at> <87fw8zvqmm.fsf@nexoid.at>\r
36         <87liir944f.fsf@qmul.ac.uk>\r
37 User-Agent: Notmuch/0.11+77~gad6d0d5 (http://notmuchmail.org) Emacs/24.1.50.2\r
38         (i686-pc-linux-gnu)\r
39 Date: Tue, 10 Jul 2012 15:30:50 +0200\r
40 Message-ID: <87a9z7vj3p.fsf@nexoid.at>\r
41 MIME-Version: 1.0\r
42 Content-Type: text/plain\r
43 X-Y-GMX-Trusted: 0\r
44 X-BeenThere: notmuch@notmuchmail.org\r
45 X-Mailman-Version: 2.1.13\r
46 Precedence: list\r
47 List-Id: "Use and development of the notmuch mail system."\r
48         <notmuch.notmuchmail.org>\r
49 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
50         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
51 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
52 List-Post: <mailto:notmuch@notmuchmail.org>\r
53 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
54 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
55         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
56 X-List-Received-Date: Tue, 10 Jul 2012 13:31:00 -0000\r
57 \r
58 As discussed in <id:20120121220407.GK16740@mit.edu>, this patch adds\r
59 support for new structured output formats (like s-expressions) by using\r
60 stateful structure_printers. An implementation of the JSON structure\r
61 printer that passes all tests is included. The output for JSON (and\r
62 text) is identical to the current output. S-Expressions will be added in\r
63 a later patch.\r
64 \r
65 A large part of this patch just implements the differentiation between\r
66 structured and non-structured output (all the code within \r
67 "if(format == unstructured_text_printer)").\r
68 \r
69 In a second patch, the structured output code should be isolated in a\r
70 separate file, and also used in all other parts of notmuch.\r
71 \r
72 The interface is a structure structure_printer, which contains the following methods:\r
73 \r
74 - initial_state: is called to create a state object, that is passed to all invocations. This should be used to keep track of the output file and everything else necessary to correctly format output.\r
75 - map: is called when a new map (associative array, dictionary) is started. map_key and the primitives (string, number, bool) are used alternatingly to add key/value pairs. pop is used to close the map (see there). This function must return a nesting level identifier that can be used to close all nested structures (maps and lists), backing out to the returned nesting level.\r
76 - list: is called when a new list (array, vector) is started. the primitives (string, number, bool) are used consecutively to add values to the list. pop is used to close the list. This function must return a nesting level identifier that can be used to close all nested structures (maps and lists), backing out to the returned nesting level.\r
77 - pop: is called to return to a given nesting level. All lists and maps with a deeper nesting level must be closed.\r
78 - number, string, bool: output one element of the specific type.\r
79 \r
80 All functions should use the state object to insert delimiters etc. automatically when appropriate.\r
81 \r
82 Example:\r
83 int top, one;\r
84 top = map(state);\r
85 map_key(state, "foo");\r
86 one = list(state);\r
87 number(state, 1);\r
88 number(state, 2);\r
89 number(state, 3);\r
90 pop(state, i);\r
91 map_key(state, "bar");\r
92 map(state);\r
93 map_key(state, "baaz");\r
94 string(state, "hello world");\r
95 pop(state, top);\r
96 \r
97 would output JSON as follows:\r
98 \r
99 {"foo": [1, 2, 3], "bar": { "baaz": "hello world"}}\r
100 ---\r
101  notmuch-search.c | 491 ++++++++++++++++++++++++++++++++++++++++---------------\r
102  1 file changed, 361 insertions(+), 130 deletions(-)\r
103 \r
104 diff --git a/notmuch-search.c b/notmuch-search.c\r
105 index 3be296d..4127777 100644\r
106 --- a/notmuch-search.c\r
107 +++ b/notmuch-search.c\r
108 @@ -28,6 +28,210 @@ typedef enum {\r
109      OUTPUT_TAGS\r
110  } output_t;\r
111  \r
112 +/* structured formatting, useful for JSON, S-Expressions, ...\r
113 +\r
114 +- initial_state: is called to create a state object, that is passed to all invocations. This should be used to keep track of the output file and everything else necessary to correctly format output.\r
115 +- map: is called when a new map (associative array, dictionary) is started. map_key and the primitives (string, number, bool) are used alternatingly to add key/value pairs. pop is used to close the map (see there). This function must return a nesting level identifier that can be used to close all nested structures (maps and lists), backing out to the returned nesting level.\r
116 +- list: is called when a new list (array, vector) is started. the primitives (string, number, bool) are used consecutively to add values to the list. pop is used to close the list. This function must return a nesting level identifier that can be used to close all nested structures (maps and lists), backing out to the returned nesting level.\r
117 +- pop: is called to return to a given nesting level. All lists and maps with a deeper nesting level must be closed.\r
118 +- number, string, bool: output one element of the specific type.\r
119 +\r
120 +All functions should use state to insert delimiters etc. automatically when appropriate.\r
121 +\r
122 +Example:\r
123 +int top, one;\r
124 +top = map(state);\r
125 +map_key(state, "foo");\r
126 +one = list(state);\r
127 +number(state, 1);\r
128 +number(state, 2);\r
129 +number(state, 3);\r
130 +pop(state, i);\r
131 +map_key(state, "bar");\r
132 +map(state);\r
133 +map_key(state, "baaz");\r
134 +string(state, "hello world");\r
135 +pop(state, top);\r
136 +\r
137 +would output JSON as follows:\r
138 +\r
139 +{"foo": [1, 2, 3], "bar": { "baaz": "hello world"}}\r
140 +\r
141 + */\r
142 +typedef struct structure_printer {\r
143 +    int (*map)(void *state);\r
144 +    int (*list)(void *state);\r
145 +    void (*pop)(void *state, int level);\r
146 +    void (*map_key)(void *state, const char *key);\r
147 +    void (*number)(void *state, int val);\r
148 +    void (*string)(void *state, const char *val);\r
149 +    void (*bool)(void *state, notmuch_bool_t val);\r
150 +    void *(*initial_state)(const struct structure_printer *sp, FILE *output);\r
151 +} structure_printer_t;\r
152 +\r
153 +/* JSON structure printer */\r
154 +\r
155 +/* single linked list implementation for keeping track of the array/map nesting state */\r
156 +typedef struct json_list {\r
157 +    int type;\r
158 +    int first_already_seen;\r
159 +    struct json_list *rest;\r
160 +} json_list_t;\r
161 +\r
162 +#define TYPE_JSON_MAP 1\r
163 +#define TYPE_JSON_ARRAY 2\r
164 +\r
165 +typedef struct json_state {\r
166 +    FILE *output;\r
167 +    json_list_t *stack;\r
168 +    int level;\r
169 +} json_state_t;\r
170 +\r
171 +int json_map(void *state);\r
172 +int json_list(void *state);\r
173 +void json_pop(void *state, int level);\r
174 +void json_map_key(void *state, const char *key);\r
175 +void json_number(void *state, int val);\r
176 +void json_string(void *state, const char *val);\r
177 +void json_bool(void *state, notmuch_bool_t val);\r
178 +void *json_initial_state(const struct structure_printer *sp, FILE *output);\r
179 +\r
180 +structure_printer_t json_structure_printer = {\r
181 +    &json_map,\r
182 +    &json_list,\r
183 +    &json_pop,\r
184 +    &json_map_key,\r
185 +    &json_number,\r
186 +    &json_string,\r
187 +    &json_bool,\r
188 +    &json_initial_state\r
189 +};\r
190 +\r
191 +int json_map(void *st) {\r
192 +    json_state_t *state = (json_state_t*)st;\r
193 +    FILE *output = state->output;\r
194 +    if(state->stack != NULL && state->stack->type == TYPE_JSON_ARRAY && state->stack->first_already_seen) {\r
195 +       fputs(",", output);\r
196 +       if(state->level == 1)\r
197 +           fputs("\n", output);\r
198 +       else\r
199 +           fputs(" ", output);\r
200 +    }\r
201 +    if(state->stack != NULL) {\r
202 +       state->stack->first_already_seen = TRUE;\r
203 +    }\r
204 +    fputs("{", output);\r
205 +    void *ctx_json_map = talloc_new (0);\r
206 +    json_list_t *el = talloc(ctx_json_map, json_list_t);\r
207 +    el->type = TYPE_JSON_MAP;\r
208 +    el->first_already_seen = FALSE;\r
209 +    el->rest = state->stack;\r
210 +    state->stack = el;\r
211 +    return state->level++;\r
212 +}\r
213 +\r
214 +int json_list(void *st) {\r
215 +    json_state_t *state = (json_state_t*)st;\r
216 +    FILE *output = state->output;\r
217 +    if(state->stack != NULL && state->stack->type == TYPE_JSON_ARRAY && state->stack->first_already_seen) {\r
218 +       fputs(",", output);\r
219 +       if(state->level == 1)\r
220 +           fputs("\n", output);\r
221 +       else\r
222 +           fputs(" ", output);\r
223 +    }\r
224 +    if(state->stack != NULL) {\r
225 +       state->stack->first_already_seen = TRUE;\r
226 +    }\r
227 +    fputs("[", output);\r
228 +    void *ctx_json_map = talloc_new (0);\r
229 +    json_list_t *el = talloc(ctx_json_map, json_list_t);\r
230 +    el->type = TYPE_JSON_ARRAY;\r
231 +    el->first_already_seen = FALSE;\r
232 +    el->rest = state->stack;\r
233 +    state->stack = el;\r
234 +    return state->level++;\r
235 +}\r
236 +\r
237 +void json_pop(void *st, int level) {\r
238 +    int i;\r
239 +    json_state_t *state = (json_state_t*)st;\r
240 +    FILE *output = state->output;\r
241 +    for(i = state->level; i > level; i--) {\r
242 +       json_list_t *tos = state->stack;\r
243 +       if(tos->type == TYPE_JSON_MAP) {\r
244 +           fputs("}", output);\r
245 +       }\r
246 +       if(tos->type == TYPE_JSON_ARRAY) {\r
247 +           fputs("]", output);\r
248 +       }\r
249 +       state->stack = tos->rest;\r
250 +       state->level--;\r
251 +       talloc_free(tos);\r
252 +    }\r
253 +    if(state->level == 0)\r
254 +       fputs("\n", output);\r
255 +}\r
256 +\r
257 +void json_map_key(void *st, const char *key) {\r
258 +    json_state_t *state = (json_state_t*)st;\r
259 +    FILE *output = state->output;\r
260 +    if(state->stack != NULL && state->stack->first_already_seen) {\r
261 +       fputs(",\n", output);\r
262 +    }\r
263 +    fputs("\"", output);\r
264 +    fputs(key, output);\r
265 +    fputs("\": ", output);\r
266 +}\r
267 +\r
268 +void json_number(void *st, int val) {\r
269 +    json_state_t *state = (json_state_t*)st;\r
270 +    FILE *output = state->output;\r
271 +    if(state->stack != NULL && state->stack->type == TYPE_JSON_ARRAY && state->stack->first_already_seen) {\r
272 +       fputs(", ", output);\r
273 +    }\r
274 +    state->stack->first_already_seen = TRUE;\r
275 +    fprintf(output, "%i", val);\r
276 +}\r
277 +\r
278 +void json_string(void *st, const char *val) {\r
279 +    json_state_t *state = (json_state_t*)st;\r
280 +    FILE *output = state->output;\r
281 +    void *ctx = talloc_new(0);\r
282 +    if(state->stack != NULL && state->stack->type == TYPE_JSON_ARRAY && state->stack->first_already_seen) {\r
283 +       fputs(",", output);\r
284 +       if(state->level == 1)\r
285 +           fputs("\n", output);\r
286 +       else\r
287 +           fputs(" ", output);\r
288 +    }\r
289 +\r
290 +    state->stack->first_already_seen = TRUE;\r
291 +    fprintf(output, "%s", json_quote_str(ctx, val));\r
292 +    talloc_free(ctx);\r
293 +}\r
294 +\r
295 +void json_bool(void *st, notmuch_bool_t val) {\r
296 +    json_state_t *state = (json_state_t*)st;\r
297 +    FILE *output = state->output;\r
298 +    if(val)\r
299 +       fputs("true", output);\r
300 +    else\r
301 +       fputs("false", output);\r
302 +}\r
303 +\r
304 +void *json_initial_state(const struct structure_printer *sp, FILE *output) {\r
305 +    (void)sp;\r
306 +    json_state_t *st = talloc(0, json_state_t);\r
307 +    st->level = 0;\r
308 +    st->stack = NULL;\r
309 +    st->output = output;\r
310 +    return st;\r
311 +}\r
312 +\r
313 +structure_printer_t *unstructured_text_printer = NULL;\r
314 +\r
315 +/* legacy, only needed for non-structured text output */\r
316  typedef struct search_format {\r
317      const char *results_start;\r
318      const char *item_start;\r
319 @@ -51,6 +255,7 @@ typedef struct search_format {\r
320      const char *results_null;\r
321  } search_format_t;\r
322  \r
323 +\r
324  static void\r
325  format_item_id_text (const void *ctx,\r
326                      const char *item_type,\r
327 @@ -64,6 +269,7 @@ format_thread_text (const void *ctx,\r
328                     const int total,\r
329                     const char *authors,\r
330                     const char *subject);\r
331 +\r
332  static const search_format_t format_text = {\r
333      "",\r
334         "",\r
335 @@ -78,35 +284,6 @@ static const search_format_t format_text = {\r
336  };\r
337  \r
338  static void\r
339 -format_item_id_json (const void *ctx,\r
340 -                    const char *item_type,\r
341 -                    const char *item_id);\r
342 -\r
343 -static void\r
344 -format_thread_json (const void *ctx,\r
345 -                   const char *thread_id,\r
346 -                   const time_t date,\r
347 -                   const int matched,\r
348 -                   const int total,\r
349 -                   const char *authors,\r
350 -                   const char *subject);\r
351 -\r
352 -/* Any changes to the JSON format should be reflected in the file\r
353 - * devel/schemata. */\r
354 -static const search_format_t format_json = {\r
355 -    "[",\r
356 -       "{",\r
357 -           format_item_id_json,\r
358 -           format_thread_json,\r
359 -           "\"tags\": [",\r
360 -               "\"%s\"", ", ",\r
361 -           "]", ",\n",\r
362 -       "}",\r
363 -    "]\n",\r
364 -    "]\n",\r
365 -};\r
366 -\r
367 -static void\r
368  format_item_id_text (unused (const void *ctx),\r
369                      const char *item_type,\r
370                      const char *item_id)\r
371 @@ -153,50 +330,9 @@ format_thread_text (const void *ctx,\r
372      talloc_free (ctx_quote);\r
373  }\r
374  \r
375 -static void\r
376 -format_item_id_json (const void *ctx,\r
377 -                    unused (const char *item_type),\r
378 -                    const char *item_id)\r
379 -{\r
380 -    void *ctx_quote = talloc_new (ctx);\r
381 -\r
382 -    printf ("%s", json_quote_str (ctx_quote, item_id));\r
383 -\r
384 -    talloc_free (ctx_quote);\r
385 -    \r
386 -}\r
387 -\r
388 -static void\r
389 -format_thread_json (const void *ctx,\r
390 -                   const char *thread_id,\r
391 -                   const time_t date,\r
392 -                   const int matched,\r
393 -                   const int total,\r
394 -                   const char *authors,\r
395 -                   const char *subject)\r
396 -{\r
397 -    void *ctx_quote = talloc_new (ctx);\r
398 -\r
399 -    printf ("\"thread\": %s,\n"\r
400 -           "\"timestamp\": %ld,\n"\r
401 -           "\"date_relative\": \"%s\",\n"\r
402 -           "\"matched\": %d,\n"\r
403 -           "\"total\": %d,\n"\r
404 -           "\"authors\": %s,\n"\r
405 -           "\"subject\": %s,\n",\r
406 -           json_quote_str (ctx_quote, thread_id),\r
407 -           date,\r
408 -           notmuch_time_relative_date (ctx, date),\r
409 -           matched,\r
410 -           total,\r
411 -           json_quote_str (ctx_quote, authors),\r
412 -           json_quote_str (ctx_quote, subject));\r
413 -\r
414 -    talloc_free (ctx_quote);\r
415 -}\r
416 -\r
417  static int\r
418 -do_search_threads (const search_format_t *format,\r
419 +do_search_threads (const structure_printer_t *format,\r
420 +                  void *state,\r
421                    notmuch_query_t *query,\r
422                    notmuch_sort_t sort,\r
423                    output_t output,\r
424 @@ -210,6 +346,8 @@ do_search_threads (const search_format_t *format,\r
425      int first_thread = 1;\r
426      int i;\r
427  \r
428 +    int outermost_level = 0;\r
429 +    int items_level = 0;\r
430      if (offset < 0) {\r
431         offset += notmuch_query_count_threads (query);\r
432         if (offset < 0)\r
433 @@ -220,7 +358,11 @@ do_search_threads (const search_format_t *format,\r
434      if (threads == NULL)\r
435         return 1;\r
436  \r
437 -    fputs (format->results_start, stdout);\r
438 +    if(format == unstructured_text_printer) {\r
439 +       fputs(format_text.results_start, stdout);\r
440 +    } else { /* structured output */\r
441 +       outermost_level = format->list(state);\r
442 +    }\r
443  \r
444      for (i = 0;\r
445          notmuch_threads_valid (threads) && (limit < 0 || i < offset + limit);\r
446 @@ -235,43 +377,92 @@ do_search_threads (const search_format_t *format,\r
447             continue;\r
448         }\r
449  \r
450 -       if (! first_thread)\r
451 -           fputs (format->item_sep, stdout);\r
452 +       if (format == unstructured_text_printer && ! first_thread)\r
453 +           fputs (format_text.item_sep, stdout);\r
454  \r
455         if (output == OUTPUT_THREADS) {\r
456 -           format->item_id (thread, "thread:",\r
457 -                            notmuch_thread_get_thread_id (thread));\r
458 +           if(format == unstructured_text_printer) {\r
459 +               format_text.item_id (thread, "thread:",\r
460 +                                    notmuch_thread_get_thread_id (thread));\r
461 +           } else { /* structured output */\r
462 +               char buffer[128];\r
463 +               snprintf(buffer, 128, "thread:%s", notmuch_thread_get_thread_id (thread));\r
464 +               format->string(state, buffer);\r
465 +           }\r
466         } else { /* output == OUTPUT_SUMMARY */\r
467 -           fputs (format->item_start, stdout);\r
468 +           int tags_level = 0;\r
469 +           void *ctx = talloc_new (0);\r
470 +\r
471 +           if(format == unstructured_text_printer) {\r
472 +               fputs (format_text.item_start, stdout);\r
473 +           } else { /* structured output */\r
474 +               items_level = format->map(state);\r
475 +           }\r
476  \r
477             if (sort == NOTMUCH_SORT_OLDEST_FIRST)\r
478                 date = notmuch_thread_get_oldest_date (thread);\r
479             else\r
480                 date = notmuch_thread_get_newest_date (thread);\r
481  \r
482 -           format->thread_summary (thread,\r
483 -                                   notmuch_thread_get_thread_id (thread),\r
484 -                                   date,\r
485 -                                   notmuch_thread_get_matched_messages (thread),\r
486 -                                   notmuch_thread_get_total_messages (thread),\r
487 -                                   notmuch_thread_get_authors (thread),\r
488 -                                   notmuch_thread_get_subject (thread));\r
489 +           if(format == unstructured_text_printer) {\r
490 +               format_text.thread_summary (thread,\r
491 +                                           notmuch_thread_get_thread_id (thread),\r
492 +                                           date,\r
493 +                                           notmuch_thread_get_matched_messages (thread),\r
494 +                                           notmuch_thread_get_total_messages (thread),\r
495 +                                           notmuch_thread_get_authors (thread),\r
496 +                                           notmuch_thread_get_subject (thread));\r
497 +           } else { /* structured output */\r
498 +               format->map_key(state, "thread");\r
499 +               format->string(state, notmuch_thread_get_thread_id (thread));\r
500 +               format->map_key(state, "timestamp");\r
501 +               format->number(state, date);\r
502 +               format->map_key(state, "date_relative");\r
503 +               format->string(state, notmuch_time_relative_date(ctx, date));\r
504 +               format->map_key(state, "matched");\r
505 +               format->number(state, notmuch_thread_get_matched_messages(thread));\r
506 +               format->map_key(state, "total");\r
507 +               format->number(state, notmuch_thread_get_total_messages(thread));\r
508 +               format->map_key(state, "authors");\r
509 +               format->string(state, notmuch_thread_get_authors(thread));\r
510 +               format->map_key(state, "subject");\r
511 +               format->string(state, notmuch_thread_get_subject(thread));\r
512 +           }\r
513 +\r
514 +           if(format == unstructured_text_printer) {\r
515 +               fputs (format_text.tag_start, stdout);\r
516 +           } else { /* structured output */\r
517 +               format->map_key(state, "tags");\r
518 +               tags_level = format->list(state);\r
519 +           }\r
520  \r
521 -           fputs (format->tag_start, stdout);\r
522  \r
523             for (tags = notmuch_thread_get_tags (thread);\r
524                  notmuch_tags_valid (tags);\r
525                  notmuch_tags_move_to_next (tags))\r
526             {\r
527 -               if (! first_tag)\r
528 -                   fputs (format->tag_sep, stdout);\r
529 -               printf (format->tag, notmuch_tags_get (tags));\r
530 +               if (format == unstructured_text_printer && ! first_tag) {\r
531 +                   fputs (format_text.tag_sep, stdout);\r
532 +               }\r
533 +               if(format == unstructured_text_printer) {\r
534 +                   printf (format_text.tag, notmuch_tags_get (tags));\r
535 +               } else { /* structured output */\r
536 +                   format->string(state, notmuch_tags_get(tags));\r
537 +               }\r
538                 first_tag = 0;\r
539             }\r
540  \r
541 -           fputs (format->tag_end, stdout);\r
542 +           if(format == unstructured_text_printer) {\r
543 +               fputs (format_text.tag_end, stdout);\r
544 +           } else { /* structured output */\r
545 +               format->pop(state, tags_level);\r
546 +           }\r
547  \r
548 -           fputs (format->item_end, stdout);\r
549 +           if(format == unstructured_text_printer) {\r
550 +               fputs (format_text.item_end, stdout);\r
551 +           } else { /* structured output */\r
552 +               format->pop(state, items_level);\r
553 +           }\r
554         }\r
555  \r
556         first_thread = 0;\r
557 @@ -279,16 +470,21 @@ do_search_threads (const search_format_t *format,\r
558         notmuch_thread_destroy (thread);\r
559      }\r
560  \r
561 -    if (first_thread)\r
562 -       fputs (format->results_null, stdout);\r
563 -    else\r
564 -       fputs (format->results_end, stdout);\r
565 +    if(format == unstructured_text_printer) {\r
566 +       if (first_thread)\r
567 +           fputs (format_text.results_null, stdout);\r
568 +       else\r
569 +           fputs (format_text.results_end, stdout);\r
570 +    } else { /* structured output */\r
571 +       format->pop(state, outermost_level);\r
572 +    }\r
573  \r
574      return 0;\r
575  }\r
576  \r
577  static int\r
578 -do_search_messages (const search_format_t *format,\r
579 +do_search_messages (const structure_printer_t *format,\r
580 +                   void *state,\r
581                     notmuch_query_t *query,\r
582                     output_t output,\r
583                     int offset,\r
584 @@ -299,6 +495,7 @@ do_search_messages (const search_format_t *format,\r
585      notmuch_filenames_t *filenames;\r
586      int first_message = 1;\r
587      int i;\r
588 +    int outermost_level = 0;\r
589  \r
590      if (offset < 0) {\r
591         offset += notmuch_query_count_messages (query);\r
592 @@ -310,7 +507,11 @@ do_search_messages (const search_format_t *format,\r
593      if (messages == NULL)\r
594         return 1;\r
595  \r
596 -    fputs (format->results_start, stdout);\r
597 +    if(format == unstructured_text_printer) {\r
598 +       fputs (format_text.results_start, stdout);\r
599 +    } else { /* structured output */\r
600 +       outermost_level = format->list(state);\r
601 +    }\r
602  \r
603      for (i = 0;\r
604          notmuch_messages_valid (messages) && (limit < 0 || i < offset + limit);\r
605 @@ -328,23 +529,32 @@ do_search_messages (const search_format_t *format,\r
606                  notmuch_filenames_valid (filenames);\r
607                  notmuch_filenames_move_to_next (filenames))\r
608             {\r
609 -               if (! first_message)\r
610 -                   fputs (format->item_sep, stdout);\r
611 +               if(format == unstructured_text_printer) {\r
612 +                   if (! first_message)\r
613 +                       fputs (format_text.item_sep, stdout);\r
614  \r
615 -               format->item_id (message, "",\r
616 -                                notmuch_filenames_get (filenames));\r
617 +                   format_text.item_id (message, "",\r
618 +                                        notmuch_filenames_get (filenames));\r
619 +               } else { /* structured output */\r
620 +               format->string(state, notmuch_filenames_get (filenames));\r
621 +               }\r
622  \r
623                 first_message = 0;\r
624             }\r
625 -           \r
626 +\r
627             notmuch_filenames_destroy( filenames );\r
628  \r
629         } else { /* output == OUTPUT_MESSAGES */\r
630 -           if (! first_message)\r
631 -               fputs (format->item_sep, stdout);\r
632 +           if(format == unstructured_text_printer) {\r
633 +               if (! first_message)\r
634 +                   fputs (format_text.item_sep, stdout);\r
635 +\r
636 +               format_text.item_id (message, "id:",\r
637 +                                    notmuch_message_get_message_id (message));\r
638 +           } else { /* structured output */\r
639 +               format->string(state, notmuch_message_get_message_id (message));\r
640 +           }\r
641  \r
642 -           format->item_id (message, "id:",\r
643 -                            notmuch_message_get_message_id (message));\r
644             first_message = 0;\r
645         }\r
646  \r
647 @@ -353,23 +563,29 @@ do_search_messages (const search_format_t *format,\r
648  \r
649      notmuch_messages_destroy (messages);\r
650  \r
651 -    if (first_message)\r
652 -       fputs (format->results_null, stdout);\r
653 -    else\r
654 -       fputs (format->results_end, stdout);\r
655 +    if(format == unstructured_text_printer) {\r
656 +       if (first_message)\r
657 +           fputs (format_text.results_null, stdout);\r
658 +       else\r
659 +           fputs (format_text.results_end, stdout);\r
660 +    } else { /* structured output */\r
661 +       format->pop(state, outermost_level);\r
662 +    }\r
663  \r
664      return 0;\r
665  }\r
666  \r
667  static int\r
668  do_search_tags (notmuch_database_t *notmuch,\r
669 -               const search_format_t *format,\r
670 +               const structure_printer_t *format,\r
671 +               void *state,\r
672                 notmuch_query_t *query)\r
673  {\r
674      notmuch_messages_t *messages = NULL;\r
675      notmuch_tags_t *tags;\r
676      const char *tag;\r
677      int first_tag = 1;\r
678 +    int outermost_level = 0;\r
679  \r
680      /* should the following only special case if no excluded terms\r
681       * specified? */\r
682 @@ -387,7 +603,11 @@ do_search_tags (notmuch_database_t *notmuch,\r
683      if (tags == NULL)\r
684         return 1;\r
685  \r
686 -    fputs (format->results_start, stdout);\r
687 +    if(format == unstructured_text_printer) {\r
688 +       fputs (format_text.results_start, stdout);\r
689 +    } else { /* structured output */\r
690 +       outermost_level = format->list(state);\r
691 +    }\r
692  \r
693      for (;\r
694          notmuch_tags_valid (tags);\r
695 @@ -395,10 +615,14 @@ do_search_tags (notmuch_database_t *notmuch,\r
696      {\r
697         tag = notmuch_tags_get (tags);\r
698  \r
699 -       if (! first_tag)\r
700 -           fputs (format->item_sep, stdout);\r
701 +       if(format == unstructured_text_printer) {\r
702 +           if (! first_tag)\r
703 +               fputs (format_text.item_sep, stdout);\r
704  \r
705 -       format->item_id (tags, "", tag);\r
706 +           format_text.item_id (tags, "", tag);\r
707 +       } else { /* structured output */\r
708 +           format->string(state, tag);\r
709 +       }\r
710  \r
711         first_tag = 0;\r
712      }\r
713 @@ -408,10 +632,14 @@ do_search_tags (notmuch_database_t *notmuch,\r
714      if (messages)\r
715         notmuch_messages_destroy (messages);\r
716  \r
717 -    if (first_tag)\r
718 -       fputs (format->results_null, stdout);\r
719 -    else\r
720 -       fputs (format->results_end, stdout);\r
721 +    if(format == unstructured_text_printer) {\r
722 +       if (first_tag)\r
723 +           fputs (format_text.results_null, stdout);\r
724 +       else\r
725 +           fputs (format_text.results_end, stdout);\r
726 +    } else { /* structured output */\r
727 +       format->pop(state, outermost_level);\r
728 +    }\r
729  \r
730      return 0;\r
731  }\r
732 @@ -430,7 +658,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\r
733      notmuch_query_t *query;\r
734      char *query_str;\r
735      notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;\r
736 -    const search_format_t *format = &format_text;\r
737 +    const structure_printer_t *format = unstructured_text_printer;\r
738 +    void *state = NULL;\r
739      int opt_index, ret;\r
740      output_t output = OUTPUT_SUMMARY;\r
741      int offset = 0;\r
742 @@ -457,11 +686,11 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\r
743                                   { "files", OUTPUT_FILES },\r
744                                   { "tags", OUTPUT_TAGS },\r
745                                   { 0, 0 } } },\r
746 -        { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x',\r
747 -          (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE },\r
748 -                                  { "false", EXCLUDE_FALSE },\r
749 -                                  { "flag", EXCLUDE_FLAG },\r
750 -                                  { 0, 0 } } },\r
751 +       { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x',\r
752 +         (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE },\r
753 +                                 { "false", EXCLUDE_FALSE },\r
754 +                                 { "flag", EXCLUDE_FLAG },\r
755 +                                 { 0, 0 } } },\r
756         { NOTMUCH_OPT_INT, &offset, "offset", 'O', 0 },\r
757         { NOTMUCH_OPT_INT, &limit, "limit", 'L', 0  },\r
758         { 0, 0, 0, 0, 0 }\r
759 @@ -475,10 +704,12 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\r
760  \r
761      switch (format_sel) {\r
762      case NOTMUCH_FORMAT_TEXT:\r
763 -       format = &format_text;\r
764 +       format = unstructured_text_printer;\r
765 +       state = 0;\r
766         break;\r
767      case NOTMUCH_FORMAT_JSON:\r
768 -       format = &format_json;\r
769 +       format = &json_structure_printer;\r
770 +       state = format->initial_state(format, stdout);\r
771         break;\r
772      }\r
773  \r
774 @@ -532,14 +763,14 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\r
775      default:\r
776      case OUTPUT_SUMMARY:\r
777      case OUTPUT_THREADS:\r
778 -       ret = do_search_threads (format, query, sort, output, offset, limit);\r
779 +       ret = do_search_threads (format, state, query, sort, output, offset, limit);\r
780         break;\r
781      case OUTPUT_MESSAGES:\r
782      case OUTPUT_FILES:\r
783 -       ret = do_search_messages (format, query, output, offset, limit);\r
784 +       ret = do_search_messages (format, state, query, output, offset, limit);\r
785         break;\r
786      case OUTPUT_TAGS:\r
787 -       ret = do_search_tags (notmuch, format, query);\r
788 +       ret = do_search_tags (notmuch, format, state, query);\r
789         break;\r
790      }\r
791  \r
792 -- \r
793 1.7.11.1\r
794 \r