--- /dev/null
+Return-Path: <craven@gmx.net>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 26710429E3C\r
+ for <notmuch@notmuchmail.org>; Wed, 11 Jul 2012 01:25:26 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0.001\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0.001 tagged_above=-999 required=5\r
+ tests=[FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001]\r
+ autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id 9a9F7MtAQ7EV for <notmuch@notmuchmail.org>;\r
+ Wed, 11 Jul 2012 01:25:23 -0700 (PDT)\r
+Received: from mailout-de.gmx.net (mailout-de.gmx.net [213.165.64.22])\r
+ by olra.theworths.org (Postfix) with SMTP id DDF20429E26\r
+ for <notmuch@notmuchmail.org>; Wed, 11 Jul 2012 01:25:21 -0700 (PDT)\r
+Received: (qmail invoked by alias); 11 Jul 2012 08:25:14 -0000\r
+Received: from gw.arelion.cust.net.lagis.at (EHLO dodekanex.arelion.at)\r
+ [83.164.197.182]\r
+ by mail.gmx.net (mp016) with SMTP; 11 Jul 2012 10:25:14 +0200\r
+X-Authenticated: #201305\r
+X-Provags-ID: V01U2FsdGVkX1887tYf0eI8tllIWkVJ6Mvejk7/HvwFk7Ruqa/5x2\r
+ jalLZfI1VUHSon\r
+Received: by dodekanex.arelion.at (Postfix, from userid 1000)\r
+ id BD7F330313C; Wed, 11 Jul 2012 10:26:36 +0200 (CEST)\r
+From: <craven@gmx.net>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH v3 2/3] Adding a structured formatter for JSON.\r
+Date: Wed, 11 Jul 2012 10:26:34 +0200\r
+Message-Id: <1341995195-2497-3-git-send-email-craven@gmx.net>\r
+X-Mailer: git-send-email 1.7.11.1\r
+In-Reply-To: <1341995195-2497-1-git-send-email-craven@gmx.net>\r
+References: <20120710191331.GE7332@mit.edu>\r
+ <1341995195-2497-1-git-send-email-craven@gmx.net>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=UTF-8\r
+Content-Transfer-Encoding: 8bit\r
+X-Y-GMX-Trusted: 0\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Wed, 11 Jul 2012 08:25:26 -0000\r
+\r
+This patch adds a structured formatter that prints exactly the same JSON as the built-in JSON printer. All tests pass without change.\r
+\r
+Notice that this formatter could be simpler by changing the whitespace\r
+and line-breaks in the generated JSON.\r
+---\r
+ Makefile.local | 1 +\r
+ structured-output.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++\r
+ structured-output.h | 59 +++++++++++++++++++\r
+ 3 files changed, 227 insertions(+)\r
+ create mode 100644 structured-output.c\r
+\r
+diff --git a/Makefile.local b/Makefile.local\r
+index a890df2..9b989dc 100644\r
+--- a/Makefile.local\r
++++ b/Makefile.local\r
+@@ -291,6 +291,7 @@ notmuch_client_srcs = \\r
+ notmuch-tag.c \\r
+ notmuch-time.c \\r
+ query-string.c \\r
++ structured-output.c \\r
+ mime-node.c \\r
+ crypto.c \\r
+ json.c\r
+diff --git a/structured-output.c b/structured-output.c\r
+new file mode 100644\r
+index 0000000..18a7306\r
+--- /dev/null\r
++++ b/structured-output.c\r
+@@ -0,0 +1,167 @@\r
++/* notmuch - Not much of an email program, (just index and search)\r
++ *\r
++ * Copyright © 2009 Carl Worth\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License as published by\r
++ * the Free Software Foundation, either version 3 of the License, or\r
++ * (at your option) any later version.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program. If not, see http://www.gnu.org/licenses/ .\r
++ *\r
++ * Author: Carl Worth <cworth@cworth.org>\r
++ */\r
++\r
++#include "structured-output.h"\r
++#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))\r
++\r
++structure_printer_t *\r
++unstructured_text_printer = NULL;\r
++\r
++structure_printer_t\r
++json_structure_printer = {\r
++ &json_map,\r
++ &json_list,\r
++ &json_pop,\r
++ &json_map_key,\r
++ &json_number,\r
++ &json_string,\r
++ &json_bool,\r
++ &json_initial_state\r
++};\r
++\r
++static int\r
++enter_level (void *st, const char *marker, int type) {\r
++ json_state_t *state = (json_state_t*)st;\r
++ FILE *output = state->output;\r
++ json_list_t *el = talloc (st, json_list_t);\r
++\r
++ json_item_separator (state, (state->level == 1) ? "\n" : " ");\r
++ fputs (marker, output);\r
++\r
++ el->type = type;\r
++ el->first_seen = FALSE;\r
++ el->rest = state->stack;\r
++ state->stack = el;\r
++ return state->level++;\r
++}\r
++\r
++void\r
++json_print_escaped_string (FILE *output, const char *val)\r
++{\r
++ static const char * const escapes[] = {\r
++ ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",\r
++ ['\f'] = "\\f", ['\n'] = "\\n", ['\t'] = "\\t"\r
++ };\r
++ fputc ('"', output);\r
++ for (; *val; ++val) {\r
++ unsigned char ch = *val;\r
++ if (ch < ARRAY_SIZE(escapes) && escapes[ch])\r
++ fputs (escapes[ch], output);\r
++ else if (ch >= 32)\r
++ fputc (ch, output);\r
++ else\r
++ fprintf (output, "\\u%04x", ch);\r
++ }\r
++ fputc ('"', output);\r
++}\r
++\r
++int\r
++json_map (void *st)\r
++{\r
++ return enter_level (st, "{", TYPE_JSON_MAP);\r
++}\r
++\r
++int\r
++json_list (void *st)\r
++{\r
++ return enter_level (st, "[", TYPE_JSON_ARRAY);\r
++}\r
++\r
++void\r
++json_pop (void *st, int level)\r
++{\r
++ json_state_t *state = (json_state_t*)st;\r
++ FILE *output = state->output;\r
++ while (state->level > level) {\r
++ json_list_t *tos = state->stack;\r
++ fputs (tos->type == TYPE_JSON_MAP ? "}" : "]", output);\r
++ state->stack = tos->rest;\r
++ state->level--;\r
++ talloc_free (tos);\r
++ }\r
++ if (state->level == 0) {\r
++ fputs ("\n", output);\r
++ }\r
++}\r
++\r
++void\r
++json_map_key (void *st, const char *key)\r
++{\r
++ json_state_t *state = (json_state_t*)st;\r
++ FILE *output = state->output;\r
++ if (state->stack != NULL && state->stack->first_seen) {\r
++ fputs (",\n", output);\r
++ }\r
++ json_print_escaped_string (output, key);\r
++ fputs (": ", output);\r
++}\r
++\r
++void\r
++json_item_separator (json_state_t *state, const char *suffix)\r
++{\r
++ FILE *output = state->output;\r
++ if (state->stack != NULL\r
++ && state->stack->type == TYPE_JSON_ARRAY\r
++ && state->stack->first_seen) {\r
++\r
++ fputs (",", output);\r
++ fputs (suffix, output);\r
++ }\r
++ if (state->stack != NULL)\r
++ state->stack->first_seen = TRUE;\r
++}\r
++\r
++void\r
++json_number (void *st, int val)\r
++{\r
++ json_state_t *state = (json_state_t*)st;\r
++ FILE *output = state->output;\r
++ json_item_separator (state, state->level == 1 ? "\n" : " ");\r
++ fprintf (output, "%d", val);\r
++}\r
++\r
++void\r
++json_string (void *st, const char *val)\r
++{\r
++ json_state_t *state = (json_state_t *)st;\r
++ FILE *output = state->output;\r
++ json_item_separator (state, state->level == 1 ? "\n" : " ");\r
++ json_print_escaped_string (output, val);\r
++}\r
++\r
++void\r
++json_bool (void *st, notmuch_bool_t val)\r
++{\r
++ json_state_t *state = (json_state_t*)st;\r
++ FILE *output = state->output;\r
++ json_item_separator (state, state->level == 1 ? "\n" : " ");\r
++ fputs (val ? "true" : "false", output);\r
++}\r
++\r
++void *\r
++json_initial_state (const struct structure_printer *sp, FILE *output)\r
++{\r
++ (void)sp;\r
++ json_state_t *st = talloc (0, json_state_t);\r
++ st->level = 0;\r
++ st->stack = NULL;\r
++ st->output = output;\r
++ return st;\r
++}\r
+diff --git a/structured-output.h b/structured-output.h\r
+index 73029f1..b211ac6 100644\r
+--- a/structured-output.h\r
++++ b/structured-output.h\r
+@@ -88,3 +88,62 @@ typedef struct structure_printer {\r
+ FILE *output);\r
+ \r
+ } structure_printer_t;\r
++\r
++/* dummy object to differentiate plain text from structured output */\r
++structure_printer_t *\r
++unstructured_text_printer;\r
++\r
++/* JSON structure printer\r
++ * An implementation of the JSON structure printer that produces\r
++ * exactly the same output as the previous JSON printer.\r
++ */\r
++\r
++/* single linked list implementation for keeping track of the array/map\r
++ * nesting state.\r
++ */\r
++typedef enum {TYPE_JSON_MAP, TYPE_JSON_ARRAY} JSON_TYPE;\r
++\r
++typedef struct json_list {\r
++ int type;\r
++ notmuch_bool_t first_seen;\r
++ struct json_list *rest;\r
++} json_list_t;\r
++\r
++typedef struct json_state {\r
++ FILE *output;\r
++ json_list_t *stack;\r
++ int level;\r
++} json_state_t;\r
++\r
++int\r
++json_map(void *state);\r
++\r
++int\r
++json_list(void *state);\r
++\r
++void\r
++json_pop(void *state, int level);\r
++\r
++void\r
++json_map_key(void *state, const char *key);\r
++\r
++void\r
++json_number(void *state, int val);\r
++\r
++void\r
++json_string(void *state, const char *val);\r
++\r
++void\r
++json_bool(void *state, notmuch_bool_t val);\r
++\r
++void *\r
++json_initial_state(const struct structure_printer *sp, FILE *output);\r
++\r
++void\r
++json_print_escaped_string(FILE *output, const char *val);\r
++\r
++void\r
++json_item_separator(json_state_t *state, const char *suffix);\r
++\r
++structure_printer_t\r
++json_structure_printer;\r
+-- \r
+1.7.11.1\r
+\r