Proposed structure formatter API
authorAustin Clements <amdragon@MIT.EDU>
Sat, 30 Jun 2012 02:59:15 +0000 (22:59 +2000)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:47:54 +0000 (09:47 -0800)
68/043d304414bb0f0ffabca0b18bedfdeb78f2a2 [new file with mode: 0644]

diff --git a/68/043d304414bb0f0ffabca0b18bedfdeb78f2a2 b/68/043d304414bb0f0ffabca0b18bedfdeb78f2a2
new file mode 100644 (file)
index 0000000..8160210
--- /dev/null
@@ -0,0 +1,341 @@
+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