Re: Proof of concept: S-Expression format

Subject: Re: Proof of concept: S-Expression format

Date: Fri, 13 Jul 2012 10:17:56 +0200

To: notmuch@notmuchmail.org

Cc:

From: craven@gmx.net


This patch shows how to add a new output format to notmuch-search.c.

As an example, it adds S-Expressions. The concrete formatting can
easily be changed, this is meant as a proof of concept that the
changes to core notmuch code are very few and all formatting state is
kept inside sprinter-sexp.c.
---
 Makefile.local   |   1 +
 notmuch-search.c |   6 +-
 sprinter-sexp.c  | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sprinter.h       |   4 ++
 4 files changed, 195 insertions(+), 1 deletion(-)
 create mode 100644 sprinter-sexp.c

diff --git a/Makefile.local b/Makefile.local
index b6c7e0c..cc1d58a 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -292,6 +292,7 @@ notmuch_client_srcs =		\
 	notmuch-time.c		\
 	sprinter-json.c		\
 	sprinter-text-search.c	\
+	sprinter-sexp.c		\
 	query-string.c		\
 	mime-node.c		\
 	crypto.c		\
diff --git a/notmuch-search.c b/notmuch-search.c
index 99fddac..2db58a5 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -256,7 +256,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
     int exclude = EXCLUDE_TRUE;
     unsigned int i;
 
-    enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT }
+    enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT, NOTMUCH_FORMAT_SEXP }
 	format_sel = NOTMUCH_FORMAT_TEXT;
 
     notmuch_opt_desc_t options[] = {
@@ -267,6 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
 				  { "text", NOTMUCH_FORMAT_TEXT },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
 	  (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
@@ -298,6 +299,9 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
     case NOTMUCH_FORMAT_JSON:
 	format = sprinter_json_create (ctx, stdout);
 	break;
+    case NOTMUCH_FORMAT_SEXP:
+	format = sprinter_sexp_create (ctx, stdout);
+	break;
     }
 
     config = notmuch_config_open (ctx, NULL, NULL);
diff --git a/sprinter-sexp.c b/sprinter-sexp.c
new file mode 100644
index 0000000..68a5db5
--- /dev/null
+++ b/sprinter-sexp.c
@@ -0,0 +1,185 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+
+typedef enum { MAP, LIST } aggregate_t;
+
+struct sprinter_sexp {
+    struct sprinter vtable;
+    FILE *stream;
+    /* Top of the state stack, or NULL if the printer is not currently
+     * inside any aggregate types. */
+    struct sexp_state *state;
+};
+
+struct sexp_state {
+    struct sexp_state *parent;
+    /* True if nothing has been printed in this aggregate yet.
+     * Suppresses the comma before a value. */
+    notmuch_bool_t first;
+    /* The character that closes the current aggregate. */
+    aggregate_t type;
+};
+
+static struct sprinter_sexp *
+sexp_begin_value (struct sprinter *sp)
+{
+    struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
+
+    if (spsx->state) {
+	if (! spsx->state->first)
+	    fputc (' ', spsx->stream);
+	else
+	    spsx->state->first = FALSE;
+    }
+    return spsx;
+}
+
+static void
+sexp_begin_aggregate (struct sprinter *sp, aggregate_t type)
+{
+    struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
+    struct sexp_state *state = talloc (spsx, struct sexp_state);
+
+    fputc ('(', spsx->stream);
+    state->parent = spsx->state;
+    state->first = TRUE;
+    state->type = type;
+
+    spsx->state = state;
+}
+
+static void
+sexp_begin_map (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp, MAP);
+}
+
+static void
+sexp_begin_list (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp, LIST);
+}
+
+static void
+sexp_end (struct sprinter *sp)
+{
+    struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
+    struct sexp_state *state = spsx->state;
+
+    fputc (')', spsx->stream);
+    spsx->state = state->parent;
+    talloc_free (state);
+    if (spsx->state == NULL)
+	fputc ('\n', spsx->stream);
+    else
+	if (spsx->state->type == MAP)
+	    fputc (')', spsx->stream);
+}
+
+static void
+sexp_string (struct sprinter *sp, const char *val)
+{
+    static const char *const escapes[] = {
+	['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
+	['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
+    };
+    struct sprinter_sexp *spsx = sexp_begin_value (sp);
+
+    fputc ('"', spsx->stream);
+    for (; *val; ++val) {
+	unsigned char ch = *val;
+	if (ch < ARRAY_SIZE (escapes) && escapes[ch])
+	    fputs (escapes[ch], spsx->stream);
+	else if (ch >= 32)
+	    fputc (ch, spsx->stream);
+	else
+	    fprintf (spsx->stream, "\\u%04x", ch);
+    }
+    fputc ('"', spsx->stream);
+    if (spsx->state != NULL &&  spsx->state->type == MAP)
+	fputc (')', spsx->stream);
+    spsx->state->first = FALSE;
+}
+
+static void
+sexp_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_sexp *spsx = sexp_begin_value (sp);
+
+    fprintf (spsx->stream, "%d", val);
+    if (spsx->state != NULL &&  spsx->state->type == MAP)
+	fputc (')', spsx->stream);
+}
+
+static void
+sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
+{
+    struct sprinter_sexp *spsx = sexp_begin_value (sp);
+
+    fputs (val ? "#t" : "#f", spsx->stream);
+    if (spsx->state != NULL &&  spsx->state->type == MAP)
+	fputc (')', spsx->stream);
+}
+
+static void
+sexp_null (struct sprinter *sp)
+{
+    struct sprinter_sexp *spsx = sexp_begin_value (sp);
+
+    fputs ("'()", spsx->stream);
+    spsx->state->first = FALSE;
+}
+
+static void
+sexp_map_key (struct sprinter *sp, const char *key)
+{
+    struct sprinter_sexp *spsx = sexp_begin_value (sp);
+
+    fputc ('(', spsx->stream);
+    fputs (key, spsx->stream);
+    fputs (" . ", spsx->stream);
+    spsx->state->first = TRUE;
+}
+
+static void
+sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
+{
+}
+
+static void
+sexp_separator (struct sprinter *sp)
+{
+    struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
+
+    fputc ('\n', spsx->stream);
+}
+
+struct sprinter *
+sprinter_sexp_create (const void *ctx, FILE *stream)
+{
+    static const struct sprinter_sexp template = {
+	.vtable = {
+	    .begin_map = sexp_begin_map,
+	    .begin_list = sexp_begin_list,
+	    .end = sexp_end,
+	    .string = sexp_string,
+	    .integer = sexp_integer,
+	    .boolean = sexp_boolean,
+	    .null = sexp_null,
+	    .map_key = sexp_map_key,
+	    .separator = sexp_separator,
+	    .set_prefix = sexp_set_prefix,
+	}
+    };
+    struct sprinter_sexp *res;
+
+    res = talloc (ctx, struct sprinter_sexp);
+    if (! res)
+	return NULL;
+
+    *res = template;
+    res->stream = stream;
+    return &res->vtable;
+}
diff --git a/sprinter.h b/sprinter.h
index 4241d65..c0146f6 100644
--- a/sprinter.h
+++ b/sprinter.h
@@ -55,4 +55,8 @@ sprinter_text_search_create (const void *ctx, FILE *stream);
 struct sprinter *
 sprinter_json_create (const void *ctx, FILE *stream);
 
+/* Create a new structure printer that emits S-Expressions. */
+struct sprinter *
+sprinter_sexp_create (const void *ctx, FILE *stream);
+
 #endif // NOTMUCH_SPRINTER_H
-- 
1.7.11.1


Thread: