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
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
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
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
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
39 Date: Tue, 10 Jul 2012 15:30:50 +0200
\r
40 Message-ID: <87a9z7vj3p.fsf@nexoid.at>
\r
42 Content-Type: text/plain
\r
44 X-BeenThere: notmuch@notmuchmail.org
\r
45 X-Mailman-Version: 2.1.13
\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
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
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
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
72 The interface is a structure structure_printer, which contains the following methods:
\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
80 All functions should use the state object to insert delimiters etc. automatically when appropriate.
\r
85 map_key(state, "foo");
\r
91 map_key(state, "bar");
\r
93 map_key(state, "baaz");
\r
94 string(state, "hello world");
\r
97 would output JSON as follows:
\r
99 {"foo": [1, 2, 3], "bar": { "baaz": "hello world"}}
\r
101 notmuch-search.c | 491 ++++++++++++++++++++++++++++++++++++++++---------------
\r
102 1 file changed, 361 insertions(+), 130 deletions(-)
\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
112 +/* structured formatting, useful for JSON, S-Expressions, ...
\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
120 +All functions should use state to insert delimiters etc. automatically when appropriate.
\r
125 +map_key(state, "foo");
\r
126 +one = list(state);
\r
131 +map_key(state, "bar");
\r
133 +map_key(state, "baaz");
\r
134 +string(state, "hello world");
\r
137 +would output JSON as follows:
\r
139 +{"foo": [1, 2, 3], "bar": { "baaz": "hello world"}}
\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
153 +/* JSON structure printer */
\r
155 +/* single linked list implementation for keeping track of the array/map nesting state */
\r
156 +typedef struct json_list {
\r
158 + int first_already_seen;
\r
159 + struct json_list *rest;
\r
162 +#define TYPE_JSON_MAP 1
\r
163 +#define TYPE_JSON_ARRAY 2
\r
165 +typedef struct json_state {
\r
167 + json_list_t *stack;
\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
180 +structure_printer_t json_structure_printer = {
\r
188 + &json_initial_state
\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
199 + fputs(" ", output);
\r
201 + if(state->stack != NULL) {
\r
202 + state->stack->first_already_seen = TRUE;
\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
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
222 + fputs(" ", output);
\r
224 + if(state->stack != NULL) {
\r
225 + state->stack->first_already_seen = TRUE;
\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
237 +void json_pop(void *st, int level) {
\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
246 + if(tos->type == TYPE_JSON_ARRAY) {
\r
247 + fputs("]", output);
\r
249 + state->stack = tos->rest;
\r
251 + talloc_free(tos);
\r
253 + if(state->level == 0)
\r
254 + fputs("\n", output);
\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
263 + fputs("\"", output);
\r
264 + fputs(key, output);
\r
265 + fputs("\": ", output);
\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
274 + state->stack->first_already_seen = TRUE;
\r
275 + fprintf(output, "%i", val);
\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
287 + fputs(" ", output);
\r
290 + state->stack->first_already_seen = TRUE;
\r
291 + fprintf(output, "%s", json_quote_str(ctx, val));
\r
292 + talloc_free(ctx);
\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
299 + fputs("true", output);
\r
301 + fputs("false", output);
\r
304 +void *json_initial_state(const struct structure_printer *sp, FILE *output) {
\r
306 + json_state_t *st = talloc(0, json_state_t);
\r
308 + st->stack = NULL;
\r
309 + st->output = output;
\r
313 +structure_printer_t *unstructured_text_printer = NULL;
\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
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
329 const char *authors,
\r
330 const char *subject);
\r
332 static const search_format_t format_text = {
\r
335 @@ -78,35 +284,6 @@ static const search_format_t format_text = {
\r
339 -format_item_id_json (const void *ctx,
\r
340 - const char *item_type,
\r
341 - const char *item_id);
\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
349 - const char *authors,
\r
350 - const char *subject);
\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
357 - format_item_id_json,
\r
358 - format_thread_json,
\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
376 -format_item_id_json (const void *ctx,
\r
377 - unused (const char *item_type),
\r
378 - const char *item_id)
\r
380 - void *ctx_quote = talloc_new (ctx);
\r
382 - printf ("%s", json_quote_str (ctx_quote, item_id));
\r
384 - talloc_free (ctx_quote);
\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
394 - const char *authors,
\r
395 - const char *subject)
\r
397 - void *ctx_quote = talloc_new (ctx);
\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
408 - notmuch_time_relative_date (ctx, date),
\r
411 - json_quote_str (ctx_quote, authors),
\r
412 - json_quote_str (ctx_quote, subject));
\r
414 - talloc_free (ctx_quote);
\r
418 -do_search_threads (const search_format_t *format,
\r
419 +do_search_threads (const structure_printer_t *format,
\r
421 notmuch_query_t *query,
\r
422 notmuch_sort_t sort,
\r
424 @@ -210,6 +346,8 @@ do_search_threads (const search_format_t *format,
\r
425 int first_thread = 1;
\r
428 + int outermost_level = 0;
\r
429 + int items_level = 0;
\r
431 offset += notmuch_query_count_threads (query);
\r
433 @@ -220,7 +358,11 @@ do_search_threads (const search_format_t *format,
\r
434 if (threads == NULL)
\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
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
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
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
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
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
477 if (sort == NOTMUCH_SORT_OLDEST_FIRST)
\r
478 date = notmuch_thread_get_oldest_date (thread);
\r
480 date = notmuch_thread_get_newest_date (thread);
\r
482 - format->thread_summary (thread,
\r
483 - notmuch_thread_get_thread_id (thread),
\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
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
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
521 - fputs (format->tag_start, stdout);
\r
523 for (tags = notmuch_thread_get_tags (thread);
\r
524 notmuch_tags_valid (tags);
\r
525 notmuch_tags_move_to_next (tags))
\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
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
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
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
557 @@ -279,16 +470,21 @@ do_search_threads (const search_format_t *format,
\r
558 notmuch_thread_destroy (thread);
\r
561 - if (first_thread)
\r
562 - fputs (format->results_null, stdout);
\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
569 + fputs (format_text.results_end, stdout);
\r
570 + } else { /* structured output */
\r
571 + format->pop(state, outermost_level);
\r
578 -do_search_messages (const search_format_t *format,
\r
579 +do_search_messages (const structure_printer_t *format,
\r
581 notmuch_query_t *query,
\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
588 + int outermost_level = 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
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
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
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
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
627 notmuch_filenames_destroy( filenames );
\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
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
642 - format->item_id (message, "id:",
\r
643 - notmuch_message_get_message_id (message));
\r
647 @@ -353,23 +563,29 @@ do_search_messages (const search_format_t *format,
\r
649 notmuch_messages_destroy (messages);
\r
651 - if (first_message)
\r
652 - fputs (format->results_null, stdout);
\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
659 + fputs (format_text.results_end, stdout);
\r
660 + } else { /* structured output */
\r
661 + format->pop(state, outermost_level);
\r
668 do_search_tags (notmuch_database_t *notmuch,
\r
669 - const search_format_t *format,
\r
670 + const structure_printer_t *format,
\r
672 notmuch_query_t *query)
\r
674 notmuch_messages_t *messages = NULL;
\r
675 notmuch_tags_t *tags;
\r
678 + int outermost_level = 0;
\r
680 /* should the following only special case if no excluded terms
\r
682 @@ -387,7 +603,11 @@ do_search_tags (notmuch_database_t *notmuch,
\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
694 notmuch_tags_valid (tags);
\r
695 @@ -395,10 +615,14 @@ do_search_tags (notmuch_database_t *notmuch,
\r
697 tag = notmuch_tags_get (tags);
\r
700 - fputs (format->item_sep, stdout);
\r
701 + if(format == unstructured_text_printer) {
\r
703 + fputs (format_text.item_sep, stdout);
\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
713 @@ -408,10 +632,14 @@ do_search_tags (notmuch_database_t *notmuch,
\r
715 notmuch_messages_destroy (messages);
\r
718 - fputs (format->results_null, stdout);
\r
720 - fputs (format->results_end, stdout);
\r
721 + if(format == unstructured_text_printer) {
\r
723 + fputs (format_text.results_null, stdout);
\r
725 + fputs (format_text.results_end, stdout);
\r
726 + } else { /* structured output */
\r
727 + format->pop(state, outermost_level);
\r
732 @@ -430,7 +658,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
733 notmuch_query_t *query;
\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
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
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
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
756 { NOTMUCH_OPT_INT, &offset, "offset", 'O', 0 },
\r
757 { NOTMUCH_OPT_INT, &limit, "limit", 'L', 0 },
\r
759 @@ -475,10 +704,12 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
761 switch (format_sel) {
\r
762 case NOTMUCH_FORMAT_TEXT:
\r
763 - format = &format_text;
\r
764 + format = unstructured_text_printer;
\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
774 @@ -532,14 +763,14 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\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
781 case OUTPUT_MESSAGES:
\r
783 - ret = do_search_messages (format, query, output, offset, limit);
\r
784 + ret = do_search_messages (format, state, query, output, offset, limit);
\r
787 - ret = do_search_tags (notmuch, format, query);
\r
788 + ret = do_search_tags (notmuch, format, state, query);
\r