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