--- /dev/null
+Return-Path: <amdragon@mit.edu>\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 E1014431FB6\r
+ for <notmuch@notmuchmail.org>; Fri, 29 Jun 2012 19:59:20 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.7\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
+ tests=[RCVD_IN_DNSWL_LOW=-0.7] 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 ojSSQFZQVlNL for <notmuch@notmuchmail.org>;\r
+ Fri, 29 Jun 2012 19:59:20 -0700 (PDT)\r
+Received: from dmz-mailsec-scanner-5.mit.edu (DMZ-MAILSEC-SCANNER-5.MIT.EDU\r
+ [18.7.68.34])\r
+ by olra.theworths.org (Postfix) with ESMTP id DC0DD431FAF\r
+ for <notmuch@notmuchmail.org>; Fri, 29 Jun 2012 19:59:19 -0700 (PDT)\r
+X-AuditID: 12074422-b7f1f6d00000090b-38-4fee6b85923a\r
+Received: from mailhub-auth-4.mit.edu ( [18.7.62.39])\r
+ by dmz-mailsec-scanner-5.mit.edu (Symantec Messaging Gateway) with SMTP\r
+ id FE.1E.02315.58B6EEF4; Fri, 29 Jun 2012 22:59:17 -0400 (EDT)\r
+Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
+ by mailhub-auth-4.mit.edu (8.13.8/8.9.2) with ESMTP id q5U2xGiF030961; \r
+ Fri, 29 Jun 2012 22:59:17 -0400\r
+Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
+ (authenticated bits=0)\r
+ (User authenticated as amdragon@ATHENA.MIT.EDU)\r
+ by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id q5U2xFLm007059\r
+ (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
+ Fri, 29 Jun 2012 22:59:16 -0400 (EDT)\r
+Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.77)\r
+ (envelope-from <amdragon@mit.edu>)\r
+ id 1Skntz-0000P7-7D; Fri, 29 Jun 2012 22:59:15 -0400\r
+From: Austin Clements <amdragon@MIT.EDU>\r
+To: Peter Feigl <craven@gmx.net>,\r
+ Jameson Graef Rollins <jrollins@finestructure.net>\r
+Subject: Proposed structure formatter API\r
+User-Agent: Notmuch/0.13.2+59~g78b98ba (http://notmuchmail.org) Emacs/23.3.1\r
+ (i486-pc-linux-gnu)\r
+Date: Fri, 29 Jun 2012 22:59:15 -0400\r
+Message-ID: <87d34hsdx8.fsf@awakening.csail.mit.edu>\r
+MIME-Version: 1.0\r
+Content-Type: multipart/mixed; boundary="=-=-="\r
+X-Brightmail-Tracker:\r
+ H4sIAAAAAAAAA+NgFvrMIsWRmVeSWpSXmKPExsUixG6nrtua/c7fYM9kbYu9De2MFnv2eVlc\r
+ vzmT2YHZ4+5pLo/Fm/azeTxbdYs5gDmKyyYlNSezLLVI3y6BK+P1+VXMBU9tK1pfH2VsYLyo\r
+ 38XIySEhYCLRuOo3O4QtJnHh3nq2LkYuDiGBfYwSB+dOZoJwNjBKrJg1hRHCOckk8XvNGqjM\r
+ EkaJrUsfMoL0swloSGzbvxzMFhGIlZj7bi8ziM0sIC3x7XczE4gtLKApsfPwU6AdHByiAgkS\r
+ B4/XgJgsAqoS07qcQUxeoIsev7UHKeYVEJQ4OfMJC8QQfYnZ07tZJzDyz0KSmoUkBWFLSpya\r
+ tpQNwjaQeLX/IuMCRpZVjLIpuVW6uYmZOcWpybrFyYl5ealFuqZ6uZkleqkppZsYwWHrorSD\r
+ 8edBpUOMAhyMSjy8UZff+guxJpYVV+YeYpTkYFIS5T2T+s5fiC8pP6UyI7E4I76oNCe1+BCj\r
+ BAezkgjvw8VA5bwpiZVVqUX5MClpDhYlcd5rKTf9hQTSE0tSs1NTC1KLYLIyHBxKEry/soCG\r
+ ChalpqdWpGXmlCCkmTg4QYbzAA1/AFLDW1yQmFucmQ6RP8WoKCXOex0kIQCSyCjNg+uFpZVX\r
+ jOJArwjzvgSp4gGmJLjuV0CDmYAGOwW8BhlckoiQkmpgrM+bsm/W5LxHz8ILV796dumAnGHB\r
+ N0GmV2cdni2x+H8gZ92i9sSLqv8fBjy6eZ/dQ1SdSftc5PnIGQr9H/8dT9c2exZ3JlXpzLor\r
+ +zxuPf/1asH565+3qUwW4Z7BaqcidOTBr0Win6/rrD4l4bjPj2fCGlbtWx/OFk/xzb3EftEn\r
+ Ofht8UIergYlluKMREMt5qLiRADJaRFhBgMAAA==\r
+Cc: notmuch@notmuchmail.org\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: Sat, 30 Jun 2012 02:59:21 -0000\r
+\r
+--=-=-=\r
+\r
+On IRC today, Peter renewed discussions of supporting an S-expression\r
+format, which I think would be a fantastic thing to have.\r
+\r
+A while back [1] I tried to explain an API I thought would form a good\r
+foundation for this by abstracting away the differences between JSON and\r
+S-expression output so both structured formats can use the same code,\r
+but I didn't express the idea very clearly. I built essentially this\r
+API for another project recently and it worked well (and I learned a few\r
+things), so I figured I would give it another shot. It's simple enough\r
+that I tossed together an example implementation, which is attached.\r
+\r
+The idea is similar to the existing format structures, but instead of\r
+dealing with complex, high-level concepts like threads or messages, this\r
+API deals with syntax-level concepts like lists and strings. All of the\r
+structured output formats can then be supported by a single high-level\r
+formatter backed by different syntax formatters.\r
+\r
+There are a few ways something like this could be integrated with the\r
+high-level formatters. For show, the least invasive thing would be to\r
+have two notmuch_show_format structures with different part functions.\r
+These part functions would be thin wrappers that simply create the right\r
+structure printer and then pass it to a common part function.\r
+\r
+It would be even nicer if we could get rid of the\r
+message_set_{start,sep,end} fields, since those duplicate functionality\r
+from the structure printer and their use is haphazard and overly\r
+complicated. We could do this by changing notmuch_show_format to\r
+something like\r
+\r
+typedef struct notmuch_show_format {\r
+ struct sprinter *(*new_sprinter) (const void *ctx);\r
+ notmuch_status_t (*part) (const void *ctx, struct sprinter *sp,\r
+ struct mime_node *node,\r
+ const struct notmuch_show_params *params);\r
+} notmuch_show_format_t;\r
+\r
+For the JSON and S-expression show formats, new_sprinter would be\r
+sprinter_json_new and sprinter_sexp_new, respectively, and they could\r
+share a part function. For the text, mbox, and raw formatters,\r
+new_sprinter could simply be NULL, or we could provide a "NULL structure\r
+printer" implementation that does nothing.\r
+\r
+We could do something similar for reply.\r
+\r
+search_format is more complicated, but might also benefit more. Most of\r
+those fields have to do with how to print lists and objects and could be\r
+removed if the format simply provided a new_sprinter method like the\r
+notmuch_show_format I suggested above.\r
+\r
+[1] id:"20120121220407.GK16740@mit.edu"\r
+\r
+\r
+--=-=-=\r
+Content-Type: text/x-csrc\r
+Content-Disposition: inline; filename=sprinter.c\r
+\r
+#include <stdbool.h>\r
+#include <stdio.h>\r
+#include <talloc.h>\r
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))\r
+\r
+/* Structure printer interface */\r
+struct sprinter\r
+{\r
+ void (*begin_map) (struct sprinter *);\r
+ void (*begin_list) (struct sprinter *);\r
+ void (*end) (struct sprinter *);\r
+\r
+ void (*string) (struct sprinter *, const char *);\r
+ void (*integer) (struct sprinter *, int);\r
+ void (*boolean) (struct sprinter *, bool);\r
+ void (*null) (struct sprinter *);\r
+ void (*map_key) (struct sprinter *, const char *);\r
+\r
+ void (*frame) (struct sprinter *);\r
+};\r
+\r
+/* Create a new structure printer that emits JSON */\r
+struct sprinter *\r
+sprinter_json_new(const void *ctx, FILE *stream);\r
+\r
+int\r
+main(int argc, char **argv)\r
+{\r
+ struct sprinter *test = sprinter_json_new (NULL, stdout);\r
+ test->begin_list (test);\r
+ test->string (test, "test");\r
+ test->integer (test, 42);\r
+ test->boolean (test, true);\r
+ test->null (test);\r
+ test->frame (test);\r
+ test->begin_map (test);\r
+ test->map_key (test, "Hello");\r
+ test->string (test, "world\n");\r
+ test->end (test);\r
+ test->frame (test);\r
+ test->end (test);\r
+ talloc_free (test);\r
+}\r
+\r
+/*\r
+ * Every below here is private implementation.\r
+ */\r
+\r
+struct sprinter_json\r
+{\r
+ struct sprinter vtable;\r
+ FILE *stream;\r
+ /* Top of the state stack, or NULL if the printer is not currently\r
+ * inside any aggregate types. */\r
+ struct json_state *state;\r
+};\r
+\r
+struct json_state\r
+{\r
+ struct json_state *parent;\r
+ /* True if nothing has been printed in this aggregate yet.\r
+ * Suppresses the comma before a value. */\r
+ bool first;\r
+ /* The character that closes the current aggregate. */\r
+ char close;\r
+};\r
+\r
+/* Helper function to set up the stream to print a value. If this\r
+ * value follows another value, prints a comma. */\r
+static struct sprinter_json *\r
+json_begin_value(struct sprinter *sp)\r
+{\r
+ struct sprinter_json *spj = (struct sprinter_json*)sp;\r
+ if (spj->state) {\r
+ if (!spj->state->first)\r
+ fputs (", ", spj->stream);\r
+ else\r
+ spj->state->first = false;\r
+ }\r
+ return spj;\r
+}\r
+\r
+/* Helper function to begin an aggregate type. Prints the open\r
+ * character and pushes a new state frame. */\r
+static void\r
+json_begin_aggregate(struct sprinter *sp, char open, char close)\r
+{\r
+ struct sprinter_json *spj = json_begin_value (sp);\r
+ struct json_state *state = talloc (spj, struct json_state);\r
+\r
+ fputc (open, spj->stream);\r
+ state->parent = spj->state;\r
+ state->first = true;\r
+ state->close = close;\r
+ spj->state = state;\r
+}\r
+\r
+static void\r
+json_begin_map(struct sprinter *sp)\r
+{\r
+ json_begin_aggregate (sp, '{', '}');\r
+}\r
+\r
+static void\r
+json_begin_list(struct sprinter *sp)\r
+{\r
+ json_begin_aggregate (sp, '[', ']');\r
+}\r
+\r
+static void\r
+json_end(struct sprinter *sp)\r
+{\r
+ struct sprinter_json *spj = (struct sprinter_json*)sp;\r
+ struct json_state *state = spj->state;\r
+\r
+ fputc (spj->state->close, spj->stream);\r
+ spj->state = state->parent;\r
+ talloc_free (state);\r
+ if(spj->state == NULL)\r
+ fputc ('\n', spj->stream);\r
+}\r
+\r
+static void\r
+json_string(struct sprinter *sp, const char *val)\r
+{\r
+ const static char * const escapes[] = {\r
+ ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",\r
+ ['\f'] = "\\f", ['\n'] = "\\n", ['\t'] = "\\t"\r
+ };\r
+ struct sprinter_json *spj = json_begin_value (sp);\r
+ fputc ('"', spj->stream);\r
+ for (; *val; ++val) {\r
+ unsigned char ch = *val;\r
+ if (ch < ARRAY_SIZE(escapes) && escapes[ch])\r
+ fputs (escapes[ch], spj->stream);\r
+ else if (ch >= 32)\r
+ fputc (ch, spj->stream);\r
+ else\r
+ fprintf (spj->stream, "\\u%04x", ch);\r
+ }\r
+ fputc ('"', spj->stream);\r
+}\r
+\r
+static void\r
+json_integer(struct sprinter *sp, int val)\r
+{\r
+ struct sprinter_json *spj = json_begin_value (sp);\r
+ fprintf (spj->stream, "%d", val);\r
+}\r
+\r
+static void\r
+json_boolean(struct sprinter *sp, bool val)\r
+{\r
+ struct sprinter_json *spj = json_begin_value (sp);\r
+ fputs (val ? "true" : "false", spj->stream);\r
+}\r
+\r
+static void\r
+json_null(struct sprinter *sp)\r
+{\r
+ struct sprinter_json *spj = json_begin_value (sp);\r
+ fputs ("null", spj->stream);\r
+}\r
+\r
+static void\r
+json_map_key(struct sprinter *sp, const char *key)\r
+{\r
+ struct sprinter_json *spj = (struct sprinter_json*)sp;\r
+ json_string (sp, key);\r
+ fputs (": ", spj->stream);\r
+ spj->state->first = true;\r
+}\r
+\r
+static void\r
+json_frame(struct sprinter *sp)\r
+{\r
+ struct sprinter_json *spj = (struct sprinter_json*)sp;\r
+ fputc ('\n', spj->stream);\r
+}\r
+\r
+struct sprinter *\r
+sprinter_json_new(const void *ctx, FILE *stream)\r
+{\r
+ const static struct sprinter_json template = {\r
+ .vtable = {\r
+ .begin_map = json_begin_map,\r
+ .begin_list = json_begin_list,\r
+ .end = json_end,\r
+ .string = json_string,\r
+ .integer = json_integer,\r
+ .boolean = json_boolean,\r
+ .null = json_null,\r
+ .map_key = json_map_key,\r
+ .frame = json_frame,\r
+ }\r
+ };\r
+ struct sprinter_json *res;\r
+\r
+ res = talloc (ctx, struct sprinter_json);\r
+ if (!res)\r
+ return NULL;\r
+\r
+ *res = template;\r
+ res->stream = stream;\r
+ return &res->vtable;\r
+}\r
+\r
+--=-=-=--\r