unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH 1/3] Adding an S-expression structured output printer.
@ 2012-11-30  8:29 Peter Feigl
  2012-11-30  8:29 ` [PATCH 2/3] Use the S-Expression structured printer for notmuch search Peter Feigl
                   ` (12 more replies)
  0 siblings, 13 replies; 38+ messages in thread
From: Peter Feigl @ 2012-11-30  8:29 UTC (permalink / raw)
  To: notmuch

This commit adds an sprinter for Lisp S-Expressions. Later commits will
use this printer.

The structure is the same as json, but:
- arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
- maps are written as a-lists: ((key "value") (other-key "other-value"))
- true is written as t
- false is written as nil
- null is written as nil
---
 Makefile.local  |   1 +
 sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 236 insertions(+)
 create mode 100644 sprinter-sexp.c

diff --git a/Makefile.local b/Makefile.local
index 2b91946..0db1713 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -270,6 +270,7 @@ notmuch_client_srcs =		\
 	notmuch-tag.c		\
 	notmuch-time.c		\
 	sprinter-json.c		\
+	sprinter-sexp.c		\
 	sprinter-text.c		\
 	query-string.c		\
 	mime-node.c		\
diff --git a/sprinter-sexp.c b/sprinter-sexp.c
new file mode 100644
index 0000000..8401c52
--- /dev/null
+++ b/sprinter-sexp.c
@@ -0,0 +1,235 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+
+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;
+
+    /* A flag to signify that a separator should be inserted in the
+     * output as soon as possible.
+     */
+    notmuch_bool_t insert_separator;
+};
+
+struct sexp_state {
+    struct sexp_state *parent;
+
+    /* True if nothing has been printed in this aggregate yet.
+     * Suppresses the space before a value. */
+    notmuch_bool_t first;
+
+    /* True if the state is a map state.
+       Used to add a space between key/value pairs. */
+    notmuch_bool_t in_map;
+
+    /* The character that closes the current aggregate. */
+    char close;
+};
+
+/* Helper function to set up the stream to print a value.  If this
+ * value follows another value, prints a space. */
+static struct sprinter_sexp *
+sexp_begin_value (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if (sps->state) {
+        if (! sps->state->first) {
+            if (sps->insert_separator) {
+                fputc ('\n', sps->stream);
+                sps->insert_separator = FALSE;
+            } else {
+                if( ! sps->state->in_map)
+                    fputc (' ', sps->stream);
+            }
+        } else {
+            sps->state->first = FALSE;
+        }
+    }
+    return sps;
+}
+
+/* Helper function to begin an aggregate type.  Prints the open
+ * character and pushes a new state frame. */
+static void
+sexp_begin_aggregate (struct sprinter *sp, char open, char close)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+    struct sexp_state *state = talloc (sps, struct sexp_state);
+    fputc (open, sps->stream);
+    state->parent = sps->state;
+    state->first = TRUE;
+    state->in_map = FALSE;
+    state->close = close;
+    sps->state = state;
+}
+
+static void
+sexp_begin_map (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    sexp_begin_aggregate (sp, '(', ')');
+    sps->state->in_map = TRUE;
+}
+
+static void
+sexp_begin_list (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp, '(', ')');
+}
+
+static void
+sexp_end (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    struct sexp_state *state = sps->state;
+
+    if (sps->state->in_map)
+        fputc (')', sps->stream);
+    fputc (sps->state->close, sps->stream);
+    sps->state = state->parent;
+    talloc_free (state);
+    if (sps->state == NULL)
+        fputc ('\n', sps->stream);
+}
+
+/* This implementation supports embedded NULs as allowed by the JSON
+ * specification and Unicode.  Support for *parsing* embedded NULs
+ * varies, but is generally not a problem outside of C-based parsers
+ * (Python's json module and Emacs' json.el take embedded NULs in
+ * stride). */
+static void
+sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
+{
+    static const char *const escapes[] = {
+        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
+        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
+    };
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    if(quote)
+        fputc ('"', sps->stream);
+    for (; len; ++val, --len) {
+        unsigned char ch = *val;
+        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
+            fputs (escapes[ch], sps->stream);
+        else if (ch >= 32)
+            fputc (ch, sps->stream);
+        else
+            fprintf (sps->stream, "\\u%04x", ch);
+    }
+    if(quote)
+        fputc ('"', sps->stream);
+}
+
+static void
+sexp_string_len (struct sprinter *sp, const char *val, size_t len)
+{
+    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
+}
+
+static void
+sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
+{
+    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
+}
+
+static void
+sexp_string (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+        val = "";
+    sexp_string_len (sp, val, strlen (val));
+}
+
+static void
+sexp_symbol (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+        val = "";
+    sexp_symbol_len (sp, val, strlen (val));
+}
+
+static void
+sexp_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fprintf (sps->stream, "%d", val);
+}
+
+static void
+sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs (val ? "t" : "nil", sps->stream);
+}
+
+static void
+sexp_null (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs ("nil", sps->stream);
+}
+
+static void
+sexp_map_key (struct sprinter *sp, const char *key)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if( sps->state->in_map && ! sps->state->first)
+        fputs (") ", sps->stream);
+    fputc ('(', sps->stream);
+    sexp_symbol (sp, key);
+    fputc (' ', sps->stream);
+}
+
+static void
+sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
+{
+}
+
+static void
+sexp_separator (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    sps->insert_separator = TRUE;
+}
+
+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,
+            .string_len = sexp_string_len,
+            .integer = sexp_integer,
+            .boolean = sexp_boolean,
+            .null = sexp_null,
+            .map_key = sexp_map_key,
+            .separator = sexp_separator,
+            .set_prefix = sexp_set_prefix,
+            .is_text_printer = FALSE,
+        }
+    };
+    struct sprinter_sexp *res;
+
+    res = talloc (ctx, struct sprinter_sexp);
+    if (! res)
+        return NULL;
+
+    *res = template;
+    res->stream = stream;
+    return &res->vtable;
+}
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 2/3] Use the S-Expression structured printer for notmuch search.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
@ 2012-11-30  8:29 ` Peter Feigl
  2012-11-30 18:04   ` Jani Nikula
  2012-11-30  8:29 ` [PATCH 3/3] Use the S-Expression structured printer in notmuch show and notmuch reply Peter Feigl
                   ` (11 subsequent siblings)
  12 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-11-30  8:29 UTC (permalink / raw)
  To: notmuch

This commit adds support for --format=sexp to notmuch search.
---
 notmuch-search.c | 6 +++++-
 sprinter.h       | 4 ++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index 830c4e4..6218622 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -305,7 +305,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[] = {
@@ -315,6 +315,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "text", NOTMUCH_FORMAT_TEXT },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
@@ -347,6 +348,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;
     default:
 	/* this should never happen */
 	INTERNAL_ERROR("no output format selected");
diff --git a/sprinter.h b/sprinter.h
index 912a526..59776a9 100644
--- a/sprinter.h
+++ b/sprinter.h
@@ -70,4 +70,8 @@ sprinter_text_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.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 3/3] Use the S-Expression structured printer in notmuch show and notmuch reply.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
  2012-11-30  8:29 ` [PATCH 2/3] Use the S-Expression structured printer for notmuch search Peter Feigl
@ 2012-11-30  8:29 ` Peter Feigl
  2012-11-30 19:06   ` Jani Nikula
  2012-11-30  8:34 ` [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-11-30  8:29 UTC (permalink / raw)
  To: notmuch

This commit changes the json printer (which doesn't do anything
json-specific anyway but just uses the structured printers) to generic
sprinters, and adds support for the printer defined in sprinter-sexp.c.
---
 notmuch-client.h |  8 ++++----
 notmuch-reply.c  | 40 ++++++++++++++++++++++++----------------
 notmuch-show.c   | 48 +++++++++++++++++++++++++++++-------------------
 3 files changed, 57 insertions(+), 39 deletions(-)

diff --git a/notmuch-client.h b/notmuch-client.h
index ae9344b..1c336dc 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -175,12 +175,12 @@ notmuch_status_t
 show_one_part (const char *filename, int part);
 
 void
-format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node,
-		  notmuch_bool_t first, notmuch_bool_t output_body);
+format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
+		      notmuch_bool_t first, notmuch_bool_t output_body);
 
 void
-format_headers_json (struct sprinter *sp, GMimeMessage *message,
-		     notmuch_bool_t reply);
+format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
+			 notmuch_bool_t reply);
 
 typedef enum {
     NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
diff --git a/notmuch-reply.c b/notmuch-reply.c
index e60a264..a7469a2 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -548,7 +548,8 @@ notmuch_reply_format_default(void *ctx,
 			     notmuch_config_t *config,
 			     notmuch_query_t *query,
 			     notmuch_show_params_t *params,
-			     notmuch_bool_t reply_all)
+			     notmuch_bool_t reply_all,
+			     unused (sprinter_t *sp))
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
@@ -587,17 +588,17 @@ notmuch_reply_format_default(void *ctx,
 }
 
 static int
-notmuch_reply_format_json(void *ctx,
-			  notmuch_config_t *config,
-			  notmuch_query_t *query,
-			  notmuch_show_params_t *params,
-			  notmuch_bool_t reply_all)
+notmuch_reply_format_sprinter(void *ctx,
+			      notmuch_config_t *config,
+			      notmuch_query_t *query,
+			      notmuch_show_params_t *params,
+			      notmuch_bool_t reply_all,
+			      sprinter_t *sp)
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
     notmuch_message_t *message;
     mime_node_t *node;
-    sprinter_t *sp;
 
     if (notmuch_query_count_messages (query) != 1) {
 	fprintf (stderr, "Error: search term did not match precisely one message.\n");
@@ -613,18 +614,17 @@ notmuch_reply_format_json(void *ctx,
     if (!reply)
 	return 1;
 
-    sp = sprinter_json_create (ctx, stdout);
     sp->begin_map (sp);
 
     /* The headers of the reply message we've created */
     sp->map_key (sp, "reply-headers");
-    format_headers_json (sp, reply, TRUE);
+    format_headers_sprinter (sp, reply, TRUE);
     g_object_unref (G_OBJECT (reply));
     reply = NULL;
 
     /* Start the original */
     sp->map_key (sp, "original");
-    format_part_json (ctx, sp, node, TRUE, TRUE);
+    format_part_sprinter (ctx, sp, node, TRUE, TRUE);
 
     /* End */
     sp->end (sp);
@@ -639,7 +639,8 @@ notmuch_reply_format_headers_only(void *ctx,
 				  notmuch_config_t *config,
 				  notmuch_query_t *query,
 				  unused (notmuch_show_params_t *params),
-				  notmuch_bool_t reply_all)
+				  notmuch_bool_t reply_all,
+				  unused (sprinter_t *sp))
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
@@ -696,6 +697,7 @@ notmuch_reply_format_headers_only(void *ctx,
 enum {
     FORMAT_DEFAULT,
     FORMAT_JSON,
+    FORMAT_SEXP,
     FORMAT_HEADERS_ONLY,
 };
 
@@ -707,7 +709,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     notmuch_query_t *query;
     char *query_string;
     int opt_index, ret = 0;
-    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
+    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all, struct sprinter *sp);
     notmuch_show_params_t params = {
 	.part = -1,
 	.crypto = {
@@ -717,11 +719,13 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     };
     int format = FORMAT_DEFAULT;
     int reply_all = TRUE;
+    struct sprinter *sp = NULL;
 
     notmuch_opt_desc_t options[] = {
 	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
 	  (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
 				  { "json", FORMAT_JSON },
+				  { "sexp", FORMAT_SEXP },
 				  { "headers-only", FORMAT_HEADERS_ONLY },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
@@ -740,9 +744,13 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 
     if (format == FORMAT_HEADERS_ONLY)
 	reply_format_func = notmuch_reply_format_headers_only;
-    else if (format == FORMAT_JSON)
-	reply_format_func = notmuch_reply_format_json;
-    else
+    else if (format == FORMAT_JSON) {
+	reply_format_func = notmuch_reply_format_sprinter;
+	sp = sprinter_json_create(ctx, stdout);
+    } else if (format == FORMAT_SEXP) {
+	reply_format_func = notmuch_reply_format_sprinter;
+	sp = sprinter_sexp_create(ctx, stdout);
+    } else
 	reply_format_func = notmuch_reply_format_default;
 
     config = notmuch_config_open (ctx, NULL, NULL);
@@ -770,7 +778,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	return 1;
     }
 
-    if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
+    if (reply_format_func (ctx, config, query, &params, reply_all, sp) != 0)
 	return 1;
 
     notmuch_crypto_cleanup (&params.crypto);
diff --git a/notmuch-show.c b/notmuch-show.c
index 2fa2292..e6da2ff 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -32,12 +32,17 @@ static const notmuch_show_format_t format_text = {
 };
 
 static notmuch_status_t
-format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
 			int indent, const notmuch_show_params_t *params);
 
 static const notmuch_show_format_t format_json = {
     .new_sprinter = sprinter_json_create,
-    .part = format_part_json_entry,
+    .part = format_part_sprinter_entry,
+};
+
+static const notmuch_show_format_t format_sexp = {
+    .new_sprinter = sprinter_sexp_create,
+    .part = format_part_sprinter_entry,
 };
 
 static notmuch_status_t
@@ -108,9 +113,9 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
 /* Emit a sequence of key/value pairs for the metadata of message.
  * The caller should begin a map before calling this. */
 static void
-format_message_json (sprinter_t *sp, notmuch_message_t *message)
+format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
 {
-    /* Any changes to the JSON format should be reflected in the file
+    /* Any changes to the JSON or S-Expression format should be reflected in the file
      * devel/schemata. */
 
     void *local = talloc_new (NULL);
@@ -208,7 +213,7 @@ _is_from_line (const char *line)
 }
 
 void
-format_headers_json (sprinter_t *sp, GMimeMessage *message,
+format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
 		     notmuch_bool_t reply)
 {
     /* Any changes to the JSON format should be reflected in the file
@@ -363,7 +368,7 @@ signer_status_to_string (GMimeSignerStatus x)
 
 #ifdef GMIME_ATLEAST_26
 static void
-format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
     /* Any changes to the JSON format should be reflected in the file
      * devel/schemata. */
@@ -438,7 +443,7 @@ format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
 }
 #else
 static void
-format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
     const GMimeSignatureValidity* validity = node->sig_validity;
 
@@ -595,7 +600,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
 }
 
 void
-format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
+format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
 		  notmuch_bool_t first, notmuch_bool_t output_body)
 {
     /* Any changes to the JSON format should be reflected in the file
@@ -603,15 +608,15 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->envelope_file) {
 	sp->begin_map (sp);
-	format_message_json (sp, node->envelope_file);
+	format_message_sprinter (sp, node->envelope_file);
 
 	sp->map_key (sp, "headers");
-	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
+	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
 
 	if (output_body) {
 	    sp->map_key (sp, "body");
 	    sp->begin_list (sp);
-	    format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE);
+	    format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE);
 	    sp->end (sp);
 	}
 	sp->end (sp);
@@ -646,7 +651,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->verify_attempted) {
 	sp->map_key (sp, "sigstatus");
-	format_part_sigstatus_json (sp, node);
+	format_part_sigstatus_sprinter (sp, node);
     }
 
     sp->map_key (sp, "content-type");
@@ -698,7 +703,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 	sp->begin_map (sp);
 
 	sp->map_key (sp, "headers");
-	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
+	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
 
 	sp->map_key (sp, "body");
 	sp->begin_list (sp);
@@ -706,7 +711,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
     }
 
     for (i = 0; i < node->nchildren; i++)
-	format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
+	format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
 
     /* Close content structures */
     for (i = 0; i < nclose; i++)
@@ -716,11 +721,11 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 }
 
 static notmuch_status_t
-format_part_json_entry (const void *ctx, sprinter_t *sp,
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
 			mime_node_t *node, unused (int indent),
 			const notmuch_show_params_t *params)
 {
-    format_part_json (ctx, sp, node, TRUE, params->output_body);
+    format_part_sprinter (ctx, sp, node, TRUE, params->output_body);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1012,6 +1017,7 @@ do_show (void *ctx,
 enum {
     NOTMUCH_FORMAT_NOT_SPECIFIED,
     NOTMUCH_FORMAT_JSON,
+    NOTMUCH_FORMAT_SEXP,
     NOTMUCH_FORMAT_TEXT,
     NOTMUCH_FORMAT_MBOX,
     NOTMUCH_FORMAT_RAW
@@ -1056,6 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
 				  { "text", NOTMUCH_FORMAT_TEXT },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "mbox", NOTMUCH_FORMAT_MBOX },
 				  { "raw", NOTMUCH_FORMAT_RAW },
 				  { 0, 0 } } },
@@ -1100,6 +1107,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
     case NOTMUCH_FORMAT_TEXT:
 	format = &format_text;
 	break;
+    case NOTMUCH_FORMAT_SEXP:
+	format = &format_sexp;
+	break;
     case NOTMUCH_FORMAT_MBOX:
 	if (params.part > 0) {
 	    fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
@@ -1120,7 +1130,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 
     /* Default is entire-thread = FALSE except for format=json. */
     if (entire_thread == ENTIRE_THREAD_DEFAULT) {
-	if (format == &format_json)
+	if (format == &format_json || format == &format_sexp)
 	    entire_thread = ENTIRE_THREAD_TRUE;
 	else
 	    entire_thread = ENTIRE_THREAD_FALSE;
@@ -1131,8 +1141,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	    fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
 	    params.output_body = TRUE;
 	} else {
-	    if (format != &format_json)
-		fprintf (stderr, "Warning: --body=false only implemented for format=json\n");
+	    if (format != &format_json && format != &format_sexp)
+		fprintf (stderr, "Warning: --body=false only implemented for format=json or format=sexp\n");
 	}
     }
 
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
  2012-11-30  8:29 ` [PATCH 2/3] Use the S-Expression structured printer for notmuch search Peter Feigl
  2012-11-30  8:29 ` [PATCH 3/3] Use the S-Expression structured printer in notmuch show and notmuch reply Peter Feigl
@ 2012-11-30  8:34 ` Peter Feigl
  2012-11-30 17:26 ` Jani Nikula
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 38+ messages in thread
From: Peter Feigl @ 2012-11-30  8:34 UTC (permalink / raw)
  To: notmuch

The reason I would like to have this included in notmuch is because I
use it for an Android client (http://www.nexoid.at/tmp/search.png).
It is written in Kawa Scheme, and works on all Android devices I have
access to :) Only reading for now, composition later.
After some cleanup I'll make it available to anyone
interested. S-Expressions are much easier to use than json here.

Greetings,

Peter

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (2 preceding siblings ...)
  2012-11-30  8:34 ` [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
@ 2012-11-30 17:26 ` Jani Nikula
  2012-11-30 19:11 ` Jani Nikula
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 38+ messages in thread
From: Jani Nikula @ 2012-11-30 17:26 UTC (permalink / raw)
  To: Peter Feigl, notmuch


Hi Peter, looks good save for a few nitpicks (see comments
inline). Someone more experienced in lisp should still have a look.

BR,
Jani.


On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit adds an sprinter for Lisp S-Expressions. Later commits will
> use this printer.
>
> The structure is the same as json, but:
> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
> - maps are written as a-lists: ((key "value") (other-key "other-value"))
> - true is written as t
> - false is written as nil
> - null is written as nil
> ---
>  Makefile.local  |   1 +
>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 236 insertions(+)
>  create mode 100644 sprinter-sexp.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 2b91946..0db1713 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>  	notmuch-tag.c		\
>  	notmuch-time.c		\
>  	sprinter-json.c		\
> +	sprinter-sexp.c		\
>  	sprinter-text.c		\
>  	query-string.c		\
>  	mime-node.c		\
> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
> new file mode 100644
> index 0000000..8401c52
> --- /dev/null
> +++ b/sprinter-sexp.c
> @@ -0,0 +1,235 @@

Copyright comment is missing. (Seems to be missing from sprinter-json.c
too, but that's no excuse! ;)

> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <talloc.h>
> +#include "sprinter.h"
> +
> +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;
> +
> +    /* A flag to signify that a separator should be inserted in the
> +     * output as soon as possible.
> +     */
> +    notmuch_bool_t insert_separator;
> +};
> +
> +struct sexp_state {
> +    struct sexp_state *parent;
> +
> +    /* True if nothing has been printed in this aggregate yet.
> +     * Suppresses the space before a value. */
> +    notmuch_bool_t first;
> +
> +    /* True if the state is a map state.
> +       Used to add a space between key/value pairs. */

The multi-line comments should use the same style here and elsewhere.

> +    notmuch_bool_t in_map;
> +
> +    /* The character that closes the current aggregate. */
> +    char close;
> +};
> +
> +/* Helper function to set up the stream to print a value.  If this
> + * value follows another value, prints a space. */
> +static struct sprinter_sexp *
> +sexp_begin_value (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if (sps->state) {
> +        if (! sps->state->first) {
> +            if (sps->insert_separator) {
> +                fputc ('\n', sps->stream);
> +                sps->insert_separator = FALSE;
> +            } else {
> +                if( ! sps->state->in_map)

Spacing should be "if (! " ...

> +                    fputc (' ', sps->stream);
> +            }
> +        } else {
> +            sps->state->first = FALSE;
> +        }
> +    }
> +    return sps;
> +}
> +
> +/* Helper function to begin an aggregate type.  Prints the open
> + * character and pushes a new state frame. */
> +static void
> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +    struct sexp_state *state = talloc (sps, struct sexp_state);
> +    fputc (open, sps->stream);
> +    state->parent = sps->state;
> +    state->first = TRUE;
> +    state->in_map = FALSE;
> +    state->close = close;
> +    sps->state = state;
> +}
> +
> +static void
> +sexp_begin_map (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    sexp_begin_aggregate (sp, '(', ')');
> +    sps->state->in_map = TRUE;
> +}
> +
> +static void
> +sexp_begin_list (struct sprinter *sp)
> +{
> +    sexp_begin_aggregate (sp, '(', ')');
> +}
> +
> +static void
> +sexp_end (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    struct sexp_state *state = sps->state;
> +
> +    if (sps->state->in_map)
> +        fputc (')', sps->stream);
> +    fputc (sps->state->close, sps->stream);
> +    sps->state = state->parent;
> +    talloc_free (state);
> +    if (sps->state == NULL)
> +        fputc ('\n', sps->stream);
> +}
> +
> +/* This implementation supports embedded NULs as allowed by the JSON
> + * specification and Unicode.  Support for *parsing* embedded NULs
> + * varies, but is generally not a problem outside of C-based parsers
> + * (Python's json module and Emacs' json.el take embedded NULs in
> + * stride). */

References to JSON seem out of place here.

> +static void
> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
> +{
> +    static const char *const escapes[] = {
> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
> +    };
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    if(quote)
> +        fputc ('"', sps->stream);
> +    for (; len; ++val, --len) {
> +        unsigned char ch = *val;
> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
> +            fputs (escapes[ch], sps->stream);
> +        else if (ch >= 32)
> +            fputc (ch, sps->stream);
> +        else
> +            fprintf (sps->stream, "\\u%04x", ch);
> +    }
> +    if(quote)
> +        fputc ('"', sps->stream);
> +}
> +
> +static void
> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
> +}
> +
> +static void
> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
> +}
> +
> +static void
> +sexp_string (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_string_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_symbol (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_symbol_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_integer (struct sprinter *sp, int val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fprintf (sps->stream, "%d", val);
> +}
> +
> +static void
> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs (val ? "t" : "nil", sps->stream);
> +}
> +
> +static void
> +sexp_null (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs ("nil", sps->stream);
> +}
> +
> +static void
> +sexp_map_key (struct sprinter *sp, const char *key)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if( sps->state->in_map && ! sps->state->first)

Spacing should be "if (sps" ...

> +        fputs (") ", sps->stream);
> +    fputc ('(', sps->stream);
> +    sexp_symbol (sp, key);
> +    fputc (' ', sps->stream);
> +}
> +
> +static void
> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
> +{
> +}
> +
> +static void
> +sexp_separator (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    sps->insert_separator = TRUE;
> +}
> +
> +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,
> +            .string_len = sexp_string_len,
> +            .integer = sexp_integer,
> +            .boolean = sexp_boolean,
> +            .null = sexp_null,
> +            .map_key = sexp_map_key,
> +            .separator = sexp_separator,
> +            .set_prefix = sexp_set_prefix,
> +            .is_text_printer = FALSE,
> +        }
> +    };
> +    struct sprinter_sexp *res;
> +
> +    res = talloc (ctx, struct sprinter_sexp);
> +    if (! res)
> +        return NULL;
> +
> +    *res = template;
> +    res->stream = stream;
> +    return &res->vtable;
> +}
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/3] Use the S-Expression structured printer for notmuch search.
  2012-11-30  8:29 ` [PATCH 2/3] Use the S-Expression structured printer for notmuch search Peter Feigl
@ 2012-11-30 18:04   ` Jani Nikula
  0 siblings, 0 replies; 38+ messages in thread
From: Jani Nikula @ 2012-11-30 18:04 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit adds support for --format=sexp to notmuch search.
> ---
>  notmuch-search.c | 6 +++++-
>  sprinter.h       | 4 ++++
>  2 files changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/notmuch-search.c b/notmuch-search.c
> index 830c4e4..6218622 100644
> --- a/notmuch-search.c
> +++ b/notmuch-search.c
> @@ -305,7 +305,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[] = {
> @@ -315,6 +315,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
>  				  { 0, 0 } } },
>  	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
>  	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
> +				  { "sexp", NOTMUCH_FORMAT_SEXP },
>  				  { "text", NOTMUCH_FORMAT_TEXT },
>  				  { 0, 0 } } },
>  	{ NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
> @@ -347,6 +348,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;
>      default:
>  	/* this should never happen */
>  	INTERNAL_ERROR("no output format selected");
> diff --git a/sprinter.h b/sprinter.h
> index 912a526..59776a9 100644
> --- a/sprinter.h
> +++ b/sprinter.h
> @@ -70,4 +70,8 @@ sprinter_text_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);
> +

This hunk belongs to patch 1/3. Otherwise LGTM.

BR,
Jani.


>  #endif // NOTMUCH_SPRINTER_H
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 3/3] Use the S-Expression structured printer in notmuch show and notmuch reply.
  2012-11-30  8:29 ` [PATCH 3/3] Use the S-Expression structured printer in notmuch show and notmuch reply Peter Feigl
@ 2012-11-30 19:06   ` Jani Nikula
  0 siblings, 0 replies; 38+ messages in thread
From: Jani Nikula @ 2012-11-30 19:06 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit changes the json printer (which doesn't do anything
> json-specific anyway but just uses the structured printers) to generic
> sprinters, and adds support for the printer defined in sprinter-sexp.c.

I think this patch should be split in two or three: First a
non-functional preparatory patch that does all the renaming and moves
sprinter creation up and passes it as parameter. Second the functional
patch(es) that add sexp support to reply and show.

Please find some other comments below.

BR,
Jani.

> ---
>  notmuch-client.h |  8 ++++----
>  notmuch-reply.c  | 40 ++++++++++++++++++++++++----------------
>  notmuch-show.c   | 48 +++++++++++++++++++++++++++++-------------------
>  3 files changed, 57 insertions(+), 39 deletions(-)
>
> diff --git a/notmuch-client.h b/notmuch-client.h
> index ae9344b..1c336dc 100644
> --- a/notmuch-client.h
> +++ b/notmuch-client.h
> @@ -175,12 +175,12 @@ notmuch_status_t
>  show_one_part (const char *filename, int part);
>  
>  void
> -format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node,
> -		  notmuch_bool_t first, notmuch_bool_t output_body);
> +format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
> +		      notmuch_bool_t first, notmuch_bool_t output_body);
>  
>  void
> -format_headers_json (struct sprinter *sp, GMimeMessage *message,
> -		     notmuch_bool_t reply);
> +format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
> +			 notmuch_bool_t reply);
>  
>  typedef enum {
>      NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
> diff --git a/notmuch-reply.c b/notmuch-reply.c
> index e60a264..a7469a2 100644
> --- a/notmuch-reply.c
> +++ b/notmuch-reply.c
> @@ -548,7 +548,8 @@ notmuch_reply_format_default(void *ctx,
>  			     notmuch_config_t *config,
>  			     notmuch_query_t *query,
>  			     notmuch_show_params_t *params,
> -			     notmuch_bool_t reply_all)
> +			     notmuch_bool_t reply_all,
> +			     unused (sprinter_t *sp))
>  {
>      GMimeMessage *reply;
>      notmuch_messages_t *messages;
> @@ -587,17 +588,17 @@ notmuch_reply_format_default(void *ctx,
>  }
>  
>  static int
> -notmuch_reply_format_json(void *ctx,
> -			  notmuch_config_t *config,
> -			  notmuch_query_t *query,
> -			  notmuch_show_params_t *params,
> -			  notmuch_bool_t reply_all)
> +notmuch_reply_format_sprinter(void *ctx,
> +			      notmuch_config_t *config,
> +			      notmuch_query_t *query,
> +			      notmuch_show_params_t *params,
> +			      notmuch_bool_t reply_all,
> +			      sprinter_t *sp)
>  {
>      GMimeMessage *reply;
>      notmuch_messages_t *messages;
>      notmuch_message_t *message;
>      mime_node_t *node;
> -    sprinter_t *sp;
>  
>      if (notmuch_query_count_messages (query) != 1) {
>  	fprintf (stderr, "Error: search term did not match precisely one message.\n");
> @@ -613,18 +614,17 @@ notmuch_reply_format_json(void *ctx,
>      if (!reply)
>  	return 1;
>  
> -    sp = sprinter_json_create (ctx, stdout);
>      sp->begin_map (sp);
>  
>      /* The headers of the reply message we've created */
>      sp->map_key (sp, "reply-headers");
> -    format_headers_json (sp, reply, TRUE);
> +    format_headers_sprinter (sp, reply, TRUE);
>      g_object_unref (G_OBJECT (reply));
>      reply = NULL;
>  
>      /* Start the original */
>      sp->map_key (sp, "original");
> -    format_part_json (ctx, sp, node, TRUE, TRUE);
> +    format_part_sprinter (ctx, sp, node, TRUE, TRUE);
>  
>      /* End */
>      sp->end (sp);
> @@ -639,7 +639,8 @@ notmuch_reply_format_headers_only(void *ctx,
>  				  notmuch_config_t *config,
>  				  notmuch_query_t *query,
>  				  unused (notmuch_show_params_t *params),
> -				  notmuch_bool_t reply_all)
> +				  notmuch_bool_t reply_all,
> +				  unused (sprinter_t *sp))
>  {
>      GMimeMessage *reply;
>      notmuch_messages_t *messages;
> @@ -696,6 +697,7 @@ notmuch_reply_format_headers_only(void *ctx,
>  enum {
>      FORMAT_DEFAULT,
>      FORMAT_JSON,
> +    FORMAT_SEXP,
>      FORMAT_HEADERS_ONLY,
>  };
>  
> @@ -707,7 +709,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>      notmuch_query_t *query;
>      char *query_string;
>      int opt_index, ret = 0;
> -    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
> +    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all, struct sprinter *sp);
>      notmuch_show_params_t params = {
>  	.part = -1,
>  	.crypto = {
> @@ -717,11 +719,13 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>      };
>      int format = FORMAT_DEFAULT;
>      int reply_all = TRUE;
> +    struct sprinter *sp = NULL;
>  
>      notmuch_opt_desc_t options[] = {
>  	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
>  	  (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
>  				  { "json", FORMAT_JSON },
> +				  { "sexp", FORMAT_SEXP },
>  				  { "headers-only", FORMAT_HEADERS_ONLY },
>  				  { 0, 0 } } },
>  	{ NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
> @@ -740,9 +744,13 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>  
>      if (format == FORMAT_HEADERS_ONLY)
>  	reply_format_func = notmuch_reply_format_headers_only;
> -    else if (format == FORMAT_JSON)
> -	reply_format_func = notmuch_reply_format_json;
> -    else
> +    else if (format == FORMAT_JSON) {
> +	reply_format_func = notmuch_reply_format_sprinter;
> +	sp = sprinter_json_create(ctx, stdout);

Space before (.

> +    } else if (format == FORMAT_SEXP) {
> +	reply_format_func = notmuch_reply_format_sprinter;
> +	sp = sprinter_sexp_create(ctx, stdout);

Ditto.

> +    } else
>  	reply_format_func = notmuch_reply_format_default;

Please add braces to all branches if you add them to some.

>  
>      config = notmuch_config_open (ctx, NULL, NULL);
> @@ -770,7 +778,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>  	return 1;
>      }
>  
> -    if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
> +    if (reply_format_func (ctx, config, query, &params, reply_all, sp) != 0)
>  	return 1;
>  
>      notmuch_crypto_cleanup (&params.crypto);
> diff --git a/notmuch-show.c b/notmuch-show.c
> index 2fa2292..e6da2ff 100644
> --- a/notmuch-show.c
> +++ b/notmuch-show.c
> @@ -32,12 +32,17 @@ static const notmuch_show_format_t format_text = {
>  };
>  
>  static notmuch_status_t
> -format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
> +format_part_sprinter_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  			int indent, const notmuch_show_params_t *params);
>  
>  static const notmuch_show_format_t format_json = {
>      .new_sprinter = sprinter_json_create,
> -    .part = format_part_json_entry,
> +    .part = format_part_sprinter_entry,
> +};
> +
> +static const notmuch_show_format_t format_sexp = {
> +    .new_sprinter = sprinter_sexp_create,
> +    .part = format_part_sprinter_entry,
>  };
>  
>  static notmuch_status_t
> @@ -108,9 +113,9 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
>  /* Emit a sequence of key/value pairs for the metadata of message.
>   * The caller should begin a map before calling this. */
>  static void
> -format_message_json (sprinter_t *sp, notmuch_message_t *message)
> +format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
>  {
> -    /* Any changes to the JSON format should be reflected in the file
> +    /* Any changes to the JSON or S-Expression format should be reflected in the file
>       * devel/schemata. */

You should probably add sexp format description to devel/schemata.

>  
>      void *local = talloc_new (NULL);
> @@ -208,7 +213,7 @@ _is_from_line (const char *line)
>  }
>  
>  void
> -format_headers_json (sprinter_t *sp, GMimeMessage *message,
> +format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
>  		     notmuch_bool_t reply)
>  {
>      /* Any changes to the JSON format should be reflected in the file
> @@ -363,7 +368,7 @@ signer_status_to_string (GMimeSignerStatus x)
>  
>  #ifdef GMIME_ATLEAST_26
>  static void
> -format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
> +format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
>  {
>      /* Any changes to the JSON format should be reflected in the file
>       * devel/schemata. */
> @@ -438,7 +443,7 @@ format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
>  }
>  #else
>  static void
> -format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
> +format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
>  {
>      const GMimeSignatureValidity* validity = node->sig_validity;
>  
> @@ -595,7 +600,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  }
>  
>  void
> -format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
> +format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  		  notmuch_bool_t first, notmuch_bool_t output_body)
>  {
>      /* Any changes to the JSON format should be reflected in the file
> @@ -603,15 +608,15 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  
>      if (node->envelope_file) {
>  	sp->begin_map (sp);
> -	format_message_json (sp, node->envelope_file);
> +	format_message_sprinter (sp, node->envelope_file);
>  
>  	sp->map_key (sp, "headers");
> -	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
> +	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
>  
>  	if (output_body) {
>  	    sp->map_key (sp, "body");
>  	    sp->begin_list (sp);
> -	    format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE);
> +	    format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE);
>  	    sp->end (sp);
>  	}
>  	sp->end (sp);
> @@ -646,7 +651,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  
>      if (node->verify_attempted) {
>  	sp->map_key (sp, "sigstatus");
> -	format_part_sigstatus_json (sp, node);
> +	format_part_sigstatus_sprinter (sp, node);
>      }
>  
>      sp->map_key (sp, "content-type");
> @@ -698,7 +703,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  	sp->begin_map (sp);
>  
>  	sp->map_key (sp, "headers");
> -	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
> +	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
>  
>  	sp->map_key (sp, "body");
>  	sp->begin_list (sp);
> @@ -706,7 +711,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>      }
>  
>      for (i = 0; i < node->nchildren; i++)
> -	format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
> +	format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
>  
>      /* Close content structures */
>      for (i = 0; i < nclose; i++)
> @@ -716,11 +721,11 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  }
>  
>  static notmuch_status_t
> -format_part_json_entry (const void *ctx, sprinter_t *sp,
> +format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
>  			mime_node_t *node, unused (int indent),
>  			const notmuch_show_params_t *params)
>  {
> -    format_part_json (ctx, sp, node, TRUE, params->output_body);
> +    format_part_sprinter (ctx, sp, node, TRUE, params->output_body);
>  
>      return NOTMUCH_STATUS_SUCCESS;
>  }
> @@ -1012,6 +1017,7 @@ do_show (void *ctx,
>  enum {
>      NOTMUCH_FORMAT_NOT_SPECIFIED,
>      NOTMUCH_FORMAT_JSON,
> +    NOTMUCH_FORMAT_SEXP,
>      NOTMUCH_FORMAT_TEXT,
>      NOTMUCH_FORMAT_MBOX,
>      NOTMUCH_FORMAT_RAW
> @@ -1056,6 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>  	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
>  	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
>  				  { "text", NOTMUCH_FORMAT_TEXT },
> +				  { "sexp", NOTMUCH_FORMAT_SEXP },
>  				  { "mbox", NOTMUCH_FORMAT_MBOX },
>  				  { "raw", NOTMUCH_FORMAT_RAW },
>  				  { 0, 0 } } },
> @@ -1100,6 +1107,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>      case NOTMUCH_FORMAT_TEXT:
>  	format = &format_text;
>  	break;
> +    case NOTMUCH_FORMAT_SEXP:
> +	format = &format_sexp;
> +	break;
>      case NOTMUCH_FORMAT_MBOX:
>  	if (params.part > 0) {
>  	    fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
> @@ -1120,7 +1130,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>  
>      /* Default is entire-thread = FALSE except for format=json. */
>      if (entire_thread == ENTIRE_THREAD_DEFAULT) {
> -	if (format == &format_json)
> +	if (format == &format_json || format == &format_sexp)
>  	    entire_thread = ENTIRE_THREAD_TRUE;
>  	else
>  	    entire_thread = ENTIRE_THREAD_FALSE;
> @@ -1131,8 +1141,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>  	    fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
>  	    params.output_body = TRUE;
>  	} else {
> -	    if (format != &format_json)
> -		fprintf (stderr, "Warning: --body=false only implemented for format=json\n");
> +	    if (format != &format_json && format != &format_sexp)
> +		fprintf (stderr, "Warning: --body=false only implemented for format=json or format=sexp\n");

s/or/and/ ?

>  	}
>      }
>  
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (3 preceding siblings ...)
  2012-11-30 17:26 ` Jani Nikula
@ 2012-11-30 19:11 ` Jani Nikula
  2012-12-01  9:59 ` Mark Walters
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 38+ messages in thread
From: Jani Nikula @ 2012-11-30 19:11 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit adds an sprinter for Lisp S-Expressions. Later commits will
> use this printer.

Overall the series looks good, apart from the small trivial
nitpicks. We'll also need tests and man pages for the cli command
changes. IMO small smoke tests are a good start and better than
nothing. This is especially important as the sexp format is not used by
other components yet. Man pages will be mostly copy paste from the json
sections.

BR,
Jani.

>
> The structure is the same as json, but:
> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
> - maps are written as a-lists: ((key "value") (other-key "other-value"))
> - true is written as t
> - false is written as nil
> - null is written as nil
> ---
>  Makefile.local  |   1 +
>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 236 insertions(+)
>  create mode 100644 sprinter-sexp.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 2b91946..0db1713 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>  	notmuch-tag.c		\
>  	notmuch-time.c		\
>  	sprinter-json.c		\
> +	sprinter-sexp.c		\
>  	sprinter-text.c		\
>  	query-string.c		\
>  	mime-node.c		\
> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
> new file mode 100644
> index 0000000..8401c52
> --- /dev/null
> +++ b/sprinter-sexp.c
> @@ -0,0 +1,235 @@
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <talloc.h>
> +#include "sprinter.h"
> +
> +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;
> +
> +    /* A flag to signify that a separator should be inserted in the
> +     * output as soon as possible.
> +     */
> +    notmuch_bool_t insert_separator;
> +};
> +
> +struct sexp_state {
> +    struct sexp_state *parent;
> +
> +    /* True if nothing has been printed in this aggregate yet.
> +     * Suppresses the space before a value. */
> +    notmuch_bool_t first;
> +
> +    /* True if the state is a map state.
> +       Used to add a space between key/value pairs. */
> +    notmuch_bool_t in_map;
> +
> +    /* The character that closes the current aggregate. */
> +    char close;
> +};
> +
> +/* Helper function to set up the stream to print a value.  If this
> + * value follows another value, prints a space. */
> +static struct sprinter_sexp *
> +sexp_begin_value (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if (sps->state) {
> +        if (! sps->state->first) {
> +            if (sps->insert_separator) {
> +                fputc ('\n', sps->stream);
> +                sps->insert_separator = FALSE;
> +            } else {
> +                if( ! sps->state->in_map)
> +                    fputc (' ', sps->stream);
> +            }
> +        } else {
> +            sps->state->first = FALSE;
> +        }
> +    }
> +    return sps;
> +}
> +
> +/* Helper function to begin an aggregate type.  Prints the open
> + * character and pushes a new state frame. */
> +static void
> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +    struct sexp_state *state = talloc (sps, struct sexp_state);
> +    fputc (open, sps->stream);
> +    state->parent = sps->state;
> +    state->first = TRUE;
> +    state->in_map = FALSE;
> +    state->close = close;
> +    sps->state = state;
> +}
> +
> +static void
> +sexp_begin_map (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    sexp_begin_aggregate (sp, '(', ')');
> +    sps->state->in_map = TRUE;
> +}
> +
> +static void
> +sexp_begin_list (struct sprinter *sp)
> +{
> +    sexp_begin_aggregate (sp, '(', ')');
> +}
> +
> +static void
> +sexp_end (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    struct sexp_state *state = sps->state;
> +
> +    if (sps->state->in_map)
> +        fputc (')', sps->stream);
> +    fputc (sps->state->close, sps->stream);
> +    sps->state = state->parent;
> +    talloc_free (state);
> +    if (sps->state == NULL)
> +        fputc ('\n', sps->stream);
> +}
> +
> +/* This implementation supports embedded NULs as allowed by the JSON
> + * specification and Unicode.  Support for *parsing* embedded NULs
> + * varies, but is generally not a problem outside of C-based parsers
> + * (Python's json module and Emacs' json.el take embedded NULs in
> + * stride). */
> +static void
> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
> +{
> +    static const char *const escapes[] = {
> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
> +    };
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    if(quote)
> +        fputc ('"', sps->stream);
> +    for (; len; ++val, --len) {
> +        unsigned char ch = *val;
> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
> +            fputs (escapes[ch], sps->stream);
> +        else if (ch >= 32)
> +            fputc (ch, sps->stream);
> +        else
> +            fprintf (sps->stream, "\\u%04x", ch);
> +    }
> +    if(quote)
> +        fputc ('"', sps->stream);
> +}
> +
> +static void
> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
> +}
> +
> +static void
> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
> +}
> +
> +static void
> +sexp_string (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_string_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_symbol (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_symbol_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_integer (struct sprinter *sp, int val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fprintf (sps->stream, "%d", val);
> +}
> +
> +static void
> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs (val ? "t" : "nil", sps->stream);
> +}
> +
> +static void
> +sexp_null (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs ("nil", sps->stream);
> +}
> +
> +static void
> +sexp_map_key (struct sprinter *sp, const char *key)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if( sps->state->in_map && ! sps->state->first)
> +        fputs (") ", sps->stream);
> +    fputc ('(', sps->stream);
> +    sexp_symbol (sp, key);
> +    fputc (' ', sps->stream);
> +}
> +
> +static void
> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
> +{
> +}
> +
> +static void
> +sexp_separator (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    sps->insert_separator = TRUE;
> +}
> +
> +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,
> +            .string_len = sexp_string_len,
> +            .integer = sexp_integer,
> +            .boolean = sexp_boolean,
> +            .null = sexp_null,
> +            .map_key = sexp_map_key,
> +            .separator = sexp_separator,
> +            .set_prefix = sexp_set_prefix,
> +            .is_text_printer = FALSE,
> +        }
> +    };
> +    struct sprinter_sexp *res;
> +
> +    res = talloc (ctx, struct sprinter_sexp);
> +    if (! res)
> +        return NULL;
> +
> +    *res = template;
> +    res->stream = stream;
> +    return &res->vtable;
> +}
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (4 preceding siblings ...)
  2012-11-30 19:11 ` Jani Nikula
@ 2012-12-01  9:59 ` Mark Walters
  2012-12-01 11:34   ` Tomi Ollila
  2012-12-01 16:14 ` Mark Walters
                   ` (6 subsequent siblings)
  12 siblings, 1 reply; 38+ messages in thread
From: Mark Walters @ 2012-12-01  9:59 UTC (permalink / raw)
  To: Peter Feigl, notmuch


Hi

Overall I like the series: I think I agree with all of Jani's
comments. 

My one extra comment is that I think we should decide on whether we also
want a sexp plist version. I think we might want one for the emacs
front-end as that currently uses plists for everything.

If we do we might want to change the names a little, both for functions
and options (eg sexp_a and sexp_p or something). Probably a lot of
sprinter-sexp would be common to both versions.

Best wishes

Mark


On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit adds an sprinter for Lisp S-Expressions. Later commits will
> use this printer.
>
> The structure is the same as json, but:
> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
> - maps are written as a-lists: ((key "value") (other-key "other-value"))
> - true is written as t
> - false is written as nil
> - null is written as nil
> ---
>  Makefile.local  |   1 +
>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 236 insertions(+)
>  create mode 100644 sprinter-sexp.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 2b91946..0db1713 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>  	notmuch-tag.c		\
>  	notmuch-time.c		\
>  	sprinter-json.c		\
> +	sprinter-sexp.c		\
>  	sprinter-text.c		\
>  	query-string.c		\
>  	mime-node.c		\
> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
> new file mode 100644
> index 0000000..8401c52
> --- /dev/null
> +++ b/sprinter-sexp.c
> @@ -0,0 +1,235 @@
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <talloc.h>
> +#include "sprinter.h"
> +
> +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;
> +
> +    /* A flag to signify that a separator should be inserted in the
> +     * output as soon as possible.
> +     */
> +    notmuch_bool_t insert_separator;
> +};
> +
> +struct sexp_state {
> +    struct sexp_state *parent;
> +
> +    /* True if nothing has been printed in this aggregate yet.
> +     * Suppresses the space before a value. */
> +    notmuch_bool_t first;
> +
> +    /* True if the state is a map state.
> +       Used to add a space between key/value pairs. */
> +    notmuch_bool_t in_map;
> +
> +    /* The character that closes the current aggregate. */
> +    char close;
> +};
> +
> +/* Helper function to set up the stream to print a value.  If this
> + * value follows another value, prints a space. */
> +static struct sprinter_sexp *
> +sexp_begin_value (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if (sps->state) {
> +        if (! sps->state->first) {
> +            if (sps->insert_separator) {
> +                fputc ('\n', sps->stream);
> +                sps->insert_separator = FALSE;
> +            } else {
> +                if( ! sps->state->in_map)
> +                    fputc (' ', sps->stream);
> +            }
> +        } else {
> +            sps->state->first = FALSE;
> +        }
> +    }
> +    return sps;
> +}
> +
> +/* Helper function to begin an aggregate type.  Prints the open
> + * character and pushes a new state frame. */
> +static void
> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +    struct sexp_state *state = talloc (sps, struct sexp_state);
> +    fputc (open, sps->stream);
> +    state->parent = sps->state;
> +    state->first = TRUE;
> +    state->in_map = FALSE;
> +    state->close = close;
> +    sps->state = state;
> +}
> +
> +static void
> +sexp_begin_map (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    sexp_begin_aggregate (sp, '(', ')');
> +    sps->state->in_map = TRUE;
> +}
> +
> +static void
> +sexp_begin_list (struct sprinter *sp)
> +{
> +    sexp_begin_aggregate (sp, '(', ')');
> +}
> +
> +static void
> +sexp_end (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    struct sexp_state *state = sps->state;
> +
> +    if (sps->state->in_map)
> +        fputc (')', sps->stream);
> +    fputc (sps->state->close, sps->stream);
> +    sps->state = state->parent;
> +    talloc_free (state);
> +    if (sps->state == NULL)
> +        fputc ('\n', sps->stream);
> +}
> +
> +/* This implementation supports embedded NULs as allowed by the JSON
> + * specification and Unicode.  Support for *parsing* embedded NULs
> + * varies, but is generally not a problem outside of C-based parsers
> + * (Python's json module and Emacs' json.el take embedded NULs in
> + * stride). */
> +static void
> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
> +{
> +    static const char *const escapes[] = {
> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
> +    };
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    if(quote)
> +        fputc ('"', sps->stream);
> +    for (; len; ++val, --len) {
> +        unsigned char ch = *val;
> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
> +            fputs (escapes[ch], sps->stream);
> +        else if (ch >= 32)
> +            fputc (ch, sps->stream);
> +        else
> +            fprintf (sps->stream, "\\u%04x", ch);
> +    }
> +    if(quote)
> +        fputc ('"', sps->stream);
> +}
> +
> +static void
> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
> +}
> +
> +static void
> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
> +}
> +
> +static void
> +sexp_string (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_string_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_symbol (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_symbol_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_integer (struct sprinter *sp, int val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fprintf (sps->stream, "%d", val);
> +}
> +
> +static void
> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs (val ? "t" : "nil", sps->stream);
> +}
> +
> +static void
> +sexp_null (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs ("nil", sps->stream);
> +}
> +
> +static void
> +sexp_map_key (struct sprinter *sp, const char *key)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if( sps->state->in_map && ! sps->state->first)
> +        fputs (") ", sps->stream);
> +    fputc ('(', sps->stream);
> +    sexp_symbol (sp, key);
> +    fputc (' ', sps->stream);
> +}
> +
> +static void
> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
> +{
> +}
> +
> +static void
> +sexp_separator (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    sps->insert_separator = TRUE;
> +}
> +
> +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,
> +            .string_len = sexp_string_len,
> +            .integer = sexp_integer,
> +            .boolean = sexp_boolean,
> +            .null = sexp_null,
> +            .map_key = sexp_map_key,
> +            .separator = sexp_separator,
> +            .set_prefix = sexp_set_prefix,
> +            .is_text_printer = FALSE,
> +        }
> +    };
> +    struct sprinter_sexp *res;
> +
> +    res = talloc (ctx, struct sprinter_sexp);
> +    if (! res)
> +        return NULL;
> +
> +    *res = template;
> +    res->stream = stream;
> +    return &res->vtable;
> +}
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01  9:59 ` Mark Walters
@ 2012-12-01 11:34   ` Tomi Ollila
  2012-12-01 12:24     ` Mark Walters
  0 siblings, 1 reply; 38+ messages in thread
From: Tomi Ollila @ 2012-12-01 11:34 UTC (permalink / raw)
  To: Mark Walters, Peter Feigl, notmuch

On Sat, Dec 01 2012, Mark Walters wrote:

> Hi
>
> Overall I like the series: I think I agree with all of Jani's
> comments. 
>
> My one extra comment is that I think we should decide on whether we also
> want a sexp plist version. I think we might want one for the emacs
> front-end as that currently uses plists for everything.
>
> If we do we might want to change the names a little, both for functions
> and options (eg sexp_a and sexp_p or something). Probably a lot of
> sprinter-sexp would be common to both versions.

This is an important question that needs to be addressed fast: options
are:

1) have options to spit both alist & plist formats
2) when converting emacs to use s-expressions, convert it to use alists
3) start using plists instead of alists in Peter's android client


In case (1) is chosen then we just need to support one more format.

How much work would it involve to convert emacs to receive content in
alists (and how feasible would that be)?

How much work would it require in Peter's client to use plists (and how
feasible would that be)?

>
> Best wishes
>
> Mark

Tomi


>
>
> On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
>> This commit adds an sprinter for Lisp S-Expressions. Later commits will
>> use this printer.
>>
>> The structure is the same as json, but:
>> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
>> - maps are written as a-lists: ((key "value") (other-key "other-value"))
>> - true is written as t
>> - false is written as nil
>> - null is written as nil
>> ---
>>  Makefile.local  |   1 +
>>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 236 insertions(+)
>>  create mode 100644 sprinter-sexp.c
>>
>> diff --git a/Makefile.local b/Makefile.local
>> index 2b91946..0db1713 100644
>> --- a/Makefile.local
>> +++ b/Makefile.local
>> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>>  	notmuch-tag.c		\
>>  	notmuch-time.c		\
>>  	sprinter-json.c		\
>> +	sprinter-sexp.c		\
>>  	sprinter-text.c		\
>>  	query-string.c		\
>>  	mime-node.c		\
>> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
>> new file mode 100644
>> index 0000000..8401c52
>> --- /dev/null
>> +++ b/sprinter-sexp.c
>> @@ -0,0 +1,235 @@
>> +#include <stdbool.h>
>> +#include <stdio.h>
>> +#include <talloc.h>
>> +#include "sprinter.h"
>> +
>> +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;
>> +
>> +    /* A flag to signify that a separator should be inserted in the
>> +     * output as soon as possible.
>> +     */
>> +    notmuch_bool_t insert_separator;
>> +};
>> +
>> +struct sexp_state {
>> +    struct sexp_state *parent;
>> +
>> +    /* True if nothing has been printed in this aggregate yet.
>> +     * Suppresses the space before a value. */
>> +    notmuch_bool_t first;
>> +
>> +    /* True if the state is a map state.
>> +       Used to add a space between key/value pairs. */
>> +    notmuch_bool_t in_map;
>> +
>> +    /* The character that closes the current aggregate. */
>> +    char close;
>> +};
>> +
>> +/* Helper function to set up the stream to print a value.  If this
>> + * value follows another value, prints a space. */
>> +static struct sprinter_sexp *
>> +sexp_begin_value (struct sprinter *sp)
>> +{
>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>> +
>> +    if (sps->state) {
>> +        if (! sps->state->first) {
>> +            if (sps->insert_separator) {
>> +                fputc ('\n', sps->stream);
>> +                sps->insert_separator = FALSE;
>> +            } else {
>> +                if( ! sps->state->in_map)
>> +                    fputc (' ', sps->stream);
>> +            }
>> +        } else {
>> +            sps->state->first = FALSE;
>> +        }
>> +    }
>> +    return sps;
>> +}
>> +
>> +/* Helper function to begin an aggregate type.  Prints the open
>> + * character and pushes a new state frame. */
>> +static void
>> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
>> +{
>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>> +    struct sexp_state *state = talloc (sps, struct sexp_state);
>> +    fputc (open, sps->stream);
>> +    state->parent = sps->state;
>> +    state->first = TRUE;
>> +    state->in_map = FALSE;
>> +    state->close = close;
>> +    sps->state = state;
>> +}
>> +
>> +static void
>> +sexp_begin_map (struct sprinter *sp)
>> +{
>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>> +    sexp_begin_aggregate (sp, '(', ')');
>> +    sps->state->in_map = TRUE;
>> +}
>> +
>> +static void
>> +sexp_begin_list (struct sprinter *sp)
>> +{
>> +    sexp_begin_aggregate (sp, '(', ')');
>> +}
>> +
>> +static void
>> +sexp_end (struct sprinter *sp)
>> +{
>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>> +    struct sexp_state *state = sps->state;
>> +
>> +    if (sps->state->in_map)
>> +        fputc (')', sps->stream);
>> +    fputc (sps->state->close, sps->stream);
>> +    sps->state = state->parent;
>> +    talloc_free (state);
>> +    if (sps->state == NULL)
>> +        fputc ('\n', sps->stream);
>> +}
>> +
>> +/* This implementation supports embedded NULs as allowed by the JSON
>> + * specification and Unicode.  Support for *parsing* embedded NULs
>> + * varies, but is generally not a problem outside of C-based parsers
>> + * (Python's json module and Emacs' json.el take embedded NULs in
>> + * stride). */
>> +static void
>> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
>> +{
>> +    static const char *const escapes[] = {
>> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
>> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
>> +    };
>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>> +
>> +    if(quote)
>> +        fputc ('"', sps->stream);
>> +    for (; len; ++val, --len) {
>> +        unsigned char ch = *val;
>> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
>> +            fputs (escapes[ch], sps->stream);
>> +        else if (ch >= 32)
>> +            fputc (ch, sps->stream);
>> +        else
>> +            fprintf (sps->stream, "\\u%04x", ch);
>> +    }
>> +    if(quote)
>> +        fputc ('"', sps->stream);
>> +}
>> +
>> +static void
>> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
>> +{
>> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
>> +}
>> +
>> +static void
>> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
>> +{
>> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
>> +}
>> +
>> +static void
>> +sexp_string (struct sprinter *sp, const char *val)
>> +{
>> +    if (val == NULL)
>> +        val = "";
>> +    sexp_string_len (sp, val, strlen (val));
>> +}
>> +
>> +static void
>> +sexp_symbol (struct sprinter *sp, const char *val)
>> +{
>> +    if (val == NULL)
>> +        val = "";
>> +    sexp_symbol_len (sp, val, strlen (val));
>> +}
>> +
>> +static void
>> +sexp_integer (struct sprinter *sp, int val)
>> +{
>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>> +
>> +    fprintf (sps->stream, "%d", val);
>> +}
>> +
>> +static void
>> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
>> +{
>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>> +
>> +    fputs (val ? "t" : "nil", sps->stream);
>> +}
>> +
>> +static void
>> +sexp_null (struct sprinter *sp)
>> +{
>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>> +
>> +    fputs ("nil", sps->stream);
>> +}
>> +
>> +static void
>> +sexp_map_key (struct sprinter *sp, const char *key)
>> +{
>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>> +
>> +    if( sps->state->in_map && ! sps->state->first)
>> +        fputs (") ", sps->stream);
>> +    fputc ('(', sps->stream);
>> +    sexp_symbol (sp, key);
>> +    fputc (' ', sps->stream);
>> +}
>> +
>> +static void
>> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
>> +{
>> +}
>> +
>> +static void
>> +sexp_separator (struct sprinter *sp)
>> +{
>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>> +
>> +    sps->insert_separator = TRUE;
>> +}
>> +
>> +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,
>> +            .string_len = sexp_string_len,
>> +            .integer = sexp_integer,
>> +            .boolean = sexp_boolean,
>> +            .null = sexp_null,
>> +            .map_key = sexp_map_key,
>> +            .separator = sexp_separator,
>> +            .set_prefix = sexp_set_prefix,
>> +            .is_text_printer = FALSE,
>> +        }
>> +    };
>> +    struct sprinter_sexp *res;
>> +
>> +    res = talloc (ctx, struct sprinter_sexp);
>> +    if (! res)
>> +        return NULL;
>> +
>> +    *res = template;
>> +    res->stream = stream;
>> +    return &res->vtable;
>> +}
>> -- 
>> 1.8.0
>>
>> _______________________________________________
>> notmuch mailing list
>> notmuch@notmuchmail.org
>> http://notmuchmail.org/mailman/listinfo/notmuch
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01 11:34   ` Tomi Ollila
@ 2012-12-01 12:24     ` Mark Walters
  2012-12-01 13:29       ` Tomi Ollila
  2012-12-01 15:18       ` Austin Clements
  0 siblings, 2 replies; 38+ messages in thread
From: Mark Walters @ 2012-12-01 12:24 UTC (permalink / raw)
  To: Tomi Ollila, Peter Feigl, notmuch


On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
> On Sat, Dec 01 2012, Mark Walters wrote:
>
>> Hi
>>
>> Overall I like the series: I think I agree with all of Jani's
>> comments. 
>>
>> My one extra comment is that I think we should decide on whether we also
>> want a sexp plist version. I think we might want one for the emacs
>> front-end as that currently uses plists for everything.
>>
>> If we do we might want to change the names a little, both for functions
>> and options (eg sexp_a and sexp_p or something). Probably a lot of
>> sprinter-sexp would be common to both versions.
>
> This is an important question that needs to be addressed fast: options
> are:
>
> 1) have options to spit both alist & plist formats
> 2) when converting emacs to use s-expressions, convert it to use alists
> 3) start using plists instead of alists in Peter's android client

Ok I have looked at this and the changes needed to output plist (or
both) are pretty small: the only functions from sprinter-sexp.c that
need to be changed are sexp_end and sexp_map_key. The total diff from
alist to plist is about 10 lines. I have a version which allows both
(the same sprinter file creates both possibilities) and have hooked it
into emacs/notmuch-show.el and it all seems to work. 

(Search is more difficult as that uses the async parser; indeed even for
show I used sexp-at-point as suggested by Tomi which seems rather
underdocumented but does seem to work)

Given the ease with which we can allow both I think that would be my
preference: the biggest problem is that slightly more cluttered option
list (i.e., we have to allow both --format=sexpa and --format=sexpp or
similar).

(I can post the patch doing the above but almost all of it is modifying the
commands to choose alist or plist rather than modifying the
sprinter-sexp itself)

Best wishes

Mark





>
>
> In case (1) is chosen then we just need to support one more format.
>
> How much work would it involve to convert emacs to receive content in
> alists (and how feasible would that be)?
>
> How much work would it require in Peter's client to use plists (and how
> feasible would that be)?
>
>>
>> Best wishes
>>
>> Mark
>
> Tomi
>
>
>>
>>
>> On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
>>> This commit adds an sprinter for Lisp S-Expressions. Later commits will
>>> use this printer.
>>>
>>> The structure is the same as json, but:
>>> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
>>> - maps are written as a-lists: ((key "value") (other-key "other-value"))
>>> - true is written as t
>>> - false is written as nil
>>> - null is written as nil
>>> ---
>>>  Makefile.local  |   1 +
>>>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>  2 files changed, 236 insertions(+)
>>>  create mode 100644 sprinter-sexp.c
>>>
>>> diff --git a/Makefile.local b/Makefile.local
>>> index 2b91946..0db1713 100644
>>> --- a/Makefile.local
>>> +++ b/Makefile.local
>>> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>>>  	notmuch-tag.c		\
>>>  	notmuch-time.c		\
>>>  	sprinter-json.c		\
>>> +	sprinter-sexp.c		\
>>>  	sprinter-text.c		\
>>>  	query-string.c		\
>>>  	mime-node.c		\
>>> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
>>> new file mode 100644
>>> index 0000000..8401c52
>>> --- /dev/null
>>> +++ b/sprinter-sexp.c
>>> @@ -0,0 +1,235 @@
>>> +#include <stdbool.h>
>>> +#include <stdio.h>
>>> +#include <talloc.h>
>>> +#include "sprinter.h"
>>> +
>>> +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;
>>> +
>>> +    /* A flag to signify that a separator should be inserted in the
>>> +     * output as soon as possible.
>>> +     */
>>> +    notmuch_bool_t insert_separator;
>>> +};
>>> +
>>> +struct sexp_state {
>>> +    struct sexp_state *parent;
>>> +
>>> +    /* True if nothing has been printed in this aggregate yet.
>>> +     * Suppresses the space before a value. */
>>> +    notmuch_bool_t first;
>>> +
>>> +    /* True if the state is a map state.
>>> +       Used to add a space between key/value pairs. */
>>> +    notmuch_bool_t in_map;
>>> +
>>> +    /* The character that closes the current aggregate. */
>>> +    char close;
>>> +};
>>> +
>>> +/* Helper function to set up the stream to print a value.  If this
>>> + * value follows another value, prints a space. */
>>> +static struct sprinter_sexp *
>>> +sexp_begin_value (struct sprinter *sp)
>>> +{
>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>> +
>>> +    if (sps->state) {
>>> +        if (! sps->state->first) {
>>> +            if (sps->insert_separator) {
>>> +                fputc ('\n', sps->stream);
>>> +                sps->insert_separator = FALSE;
>>> +            } else {
>>> +                if( ! sps->state->in_map)
>>> +                    fputc (' ', sps->stream);
>>> +            }
>>> +        } else {
>>> +            sps->state->first = FALSE;
>>> +        }
>>> +    }
>>> +    return sps;
>>> +}
>>> +
>>> +/* Helper function to begin an aggregate type.  Prints the open
>>> + * character and pushes a new state frame. */
>>> +static void
>>> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
>>> +{
>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>> +    struct sexp_state *state = talloc (sps, struct sexp_state);
>>> +    fputc (open, sps->stream);
>>> +    state->parent = sps->state;
>>> +    state->first = TRUE;
>>> +    state->in_map = FALSE;
>>> +    state->close = close;
>>> +    sps->state = state;
>>> +}
>>> +
>>> +static void
>>> +sexp_begin_map (struct sprinter *sp)
>>> +{
>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>> +    sexp_begin_aggregate (sp, '(', ')');
>>> +    sps->state->in_map = TRUE;
>>> +}
>>> +
>>> +static void
>>> +sexp_begin_list (struct sprinter *sp)
>>> +{
>>> +    sexp_begin_aggregate (sp, '(', ')');
>>> +}
>>> +
>>> +static void
>>> +sexp_end (struct sprinter *sp)
>>> +{
>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>> +    struct sexp_state *state = sps->state;
>>> +
>>> +    if (sps->state->in_map)
>>> +        fputc (')', sps->stream);
>>> +    fputc (sps->state->close, sps->stream);
>>> +    sps->state = state->parent;
>>> +    talloc_free (state);
>>> +    if (sps->state == NULL)
>>> +        fputc ('\n', sps->stream);
>>> +}
>>> +
>>> +/* This implementation supports embedded NULs as allowed by the JSON
>>> + * specification and Unicode.  Support for *parsing* embedded NULs
>>> + * varies, but is generally not a problem outside of C-based parsers
>>> + * (Python's json module and Emacs' json.el take embedded NULs in
>>> + * stride). */
>>> +static void
>>> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
>>> +{
>>> +    static const char *const escapes[] = {
>>> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
>>> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
>>> +    };
>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>> +
>>> +    if(quote)
>>> +        fputc ('"', sps->stream);
>>> +    for (; len; ++val, --len) {
>>> +        unsigned char ch = *val;
>>> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
>>> +            fputs (escapes[ch], sps->stream);
>>> +        else if (ch >= 32)
>>> +            fputc (ch, sps->stream);
>>> +        else
>>> +            fprintf (sps->stream, "\\u%04x", ch);
>>> +    }
>>> +    if(quote)
>>> +        fputc ('"', sps->stream);
>>> +}
>>> +
>>> +static void
>>> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
>>> +{
>>> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
>>> +}
>>> +
>>> +static void
>>> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
>>> +{
>>> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
>>> +}
>>> +
>>> +static void
>>> +sexp_string (struct sprinter *sp, const char *val)
>>> +{
>>> +    if (val == NULL)
>>> +        val = "";
>>> +    sexp_string_len (sp, val, strlen (val));
>>> +}
>>> +
>>> +static void
>>> +sexp_symbol (struct sprinter *sp, const char *val)
>>> +{
>>> +    if (val == NULL)
>>> +        val = "";
>>> +    sexp_symbol_len (sp, val, strlen (val));
>>> +}
>>> +
>>> +static void
>>> +sexp_integer (struct sprinter *sp, int val)
>>> +{
>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>> +
>>> +    fprintf (sps->stream, "%d", val);
>>> +}
>>> +
>>> +static void
>>> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
>>> +{
>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>> +
>>> +    fputs (val ? "t" : "nil", sps->stream);
>>> +}
>>> +
>>> +static void
>>> +sexp_null (struct sprinter *sp)
>>> +{
>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>> +
>>> +    fputs ("nil", sps->stream);
>>> +}
>>> +
>>> +static void
>>> +sexp_map_key (struct sprinter *sp, const char *key)
>>> +{
>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>> +
>>> +    if( sps->state->in_map && ! sps->state->first)
>>> +        fputs (") ", sps->stream);
>>> +    fputc ('(', sps->stream);
>>> +    sexp_symbol (sp, key);
>>> +    fputc (' ', sps->stream);
>>> +}
>>> +
>>> +static void
>>> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
>>> +{
>>> +}
>>> +
>>> +static void
>>> +sexp_separator (struct sprinter *sp)
>>> +{
>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>> +
>>> +    sps->insert_separator = TRUE;
>>> +}
>>> +
>>> +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,
>>> +            .string_len = sexp_string_len,
>>> +            .integer = sexp_integer,
>>> +            .boolean = sexp_boolean,
>>> +            .null = sexp_null,
>>> +            .map_key = sexp_map_key,
>>> +            .separator = sexp_separator,
>>> +            .set_prefix = sexp_set_prefix,
>>> +            .is_text_printer = FALSE,
>>> +        }
>>> +    };
>>> +    struct sprinter_sexp *res;
>>> +
>>> +    res = talloc (ctx, struct sprinter_sexp);
>>> +    if (! res)
>>> +        return NULL;
>>> +
>>> +    *res = template;
>>> +    res->stream = stream;
>>> +    return &res->vtable;
>>> +}
>>> -- 
>>> 1.8.0
>>>
>>> _______________________________________________
>>> notmuch mailing list
>>> notmuch@notmuchmail.org
>>> http://notmuchmail.org/mailman/listinfo/notmuch
>> _______________________________________________
>> notmuch mailing list
>> notmuch@notmuchmail.org
>> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01 12:24     ` Mark Walters
@ 2012-12-01 13:29       ` Tomi Ollila
  2012-12-01 13:45         ` Mark Walters
  2012-12-01 15:18       ` Austin Clements
  1 sibling, 1 reply; 38+ messages in thread
From: Tomi Ollila @ 2012-12-01 13:29 UTC (permalink / raw)
  To: Mark Walters, Peter Feigl, notmuch

On Sat, Dec 01 2012, Mark Walters <markwalters1009@gmail.com> wrote:

> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>> On Sat, Dec 01 2012, Mark Walters wrote:
>>
>>> Hi
>>>
>>> Overall I like the series: I think I agree with all of Jani's
>>> comments. 
>>>
>>> My one extra comment is that I think we should decide on whether we also
>>> want a sexp plist version. I think we might want one for the emacs
>>> front-end as that currently uses plists for everything.
>>>
>>> If we do we might want to change the names a little, both for functions
>>> and options (eg sexp_a and sexp_p or something). Probably a lot of
>>> sprinter-sexp would be common to both versions.
>>
>> This is an important question that needs to be addressed fast: options
>> are:
>>
>> 1) have options to spit both alist & plist formats
>> 2) when converting emacs to use s-expressions, convert it to use alists
>> 3) start using plists instead of alists in Peter's android client
>
> Ok I have looked at this and the changes needed to output plist (or
> both) are pretty small: the only functions from sprinter-sexp.c that
> need to be changed are sexp_end and sexp_map_key. The total diff from
> alist to plist is about 10 lines. I have a version which allows both
> (the same sprinter file creates both possibilities) and have hooked it
> into emacs/notmuch-show.el and it all seems to work. 
>
> (Search is more difficult as that uses the async parser; indeed even for
> show I used sexp-at-point as suggested by Tomi which seems rather
> underdocumented but does seem to work)
>
> Given the ease with which we can allow both I think that would be my
> preference: the biggest problem is that slightly more cluttered option
> list (i.e., we have to allow both --format=sexpa and --format=sexpp or
> similar).
>
> (I can post the patch doing the above but almost all of it is modifying the
> commands to choose alist or plist rather than modifying the
> sprinter-sexp itself)

As the diff is so small I agree that supporting 2 formats is good
option.

In case this is done I suggest that we proceed the following way:

1) Agree how we call these formats (sexpa & sexpp or something else)
2) Peter does his updates, including to call the format as will be decided
3) Mark posts his patches after Peter's work is pushed

>
> Best wishes
>
> Mark

Tomi


>
>
>
>
>
>>
>>
>> In case (1) is chosen then we just need to support one more format.
>>
>> How much work would it involve to convert emacs to receive content in
>> alists (and how feasible would that be)?
>>
>> How much work would it require in Peter's client to use plists (and how
>> feasible would that be)?
>>
>>>
>>> Best wishes
>>>
>>> Mark
>>
>> Tomi
>>
>>
>>>
>>>
>>> On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
>>>> This commit adds an sprinter for Lisp S-Expressions. Later commits will
>>>> use this printer.
>>>>
>>>> The structure is the same as json, but:
>>>> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
>>>> - maps are written as a-lists: ((key "value") (other-key "other-value"))
>>>> - true is written as t
>>>> - false is written as nil
>>>> - null is written as nil
>>>> ---
>>>>  Makefile.local  |   1 +
>>>>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>>  2 files changed, 236 insertions(+)
>>>>  create mode 100644 sprinter-sexp.c
>>>>
>>>> diff --git a/Makefile.local b/Makefile.local
>>>> index 2b91946..0db1713 100644
>>>> --- a/Makefile.local
>>>> +++ b/Makefile.local
>>>> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>>>>  	notmuch-tag.c		\
>>>>  	notmuch-time.c		\
>>>>  	sprinter-json.c		\
>>>> +	sprinter-sexp.c		\
>>>>  	sprinter-text.c		\
>>>>  	query-string.c		\
>>>>  	mime-node.c		\
>>>> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
>>>> new file mode 100644
>>>> index 0000000..8401c52
>>>> --- /dev/null
>>>> +++ b/sprinter-sexp.c
>>>> @@ -0,0 +1,235 @@
>>>> +#include <stdbool.h>
>>>> +#include <stdio.h>
>>>> +#include <talloc.h>
>>>> +#include "sprinter.h"
>>>> +
>>>> +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;
>>>> +
>>>> +    /* A flag to signify that a separator should be inserted in the
>>>> +     * output as soon as possible.
>>>> +     */
>>>> +    notmuch_bool_t insert_separator;
>>>> +};
>>>> +
>>>> +struct sexp_state {
>>>> +    struct sexp_state *parent;
>>>> +
>>>> +    /* True if nothing has been printed in this aggregate yet.
>>>> +     * Suppresses the space before a value. */
>>>> +    notmuch_bool_t first;
>>>> +
>>>> +    /* True if the state is a map state.
>>>> +       Used to add a space between key/value pairs. */
>>>> +    notmuch_bool_t in_map;
>>>> +
>>>> +    /* The character that closes the current aggregate. */
>>>> +    char close;
>>>> +};
>>>> +
>>>> +/* Helper function to set up the stream to print a value.  If this
>>>> + * value follows another value, prints a space. */
>>>> +static struct sprinter_sexp *
>>>> +sexp_begin_value (struct sprinter *sp)
>>>> +{
>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>> +
>>>> +    if (sps->state) {
>>>> +        if (! sps->state->first) {
>>>> +            if (sps->insert_separator) {
>>>> +                fputc ('\n', sps->stream);
>>>> +                sps->insert_separator = FALSE;
>>>> +            } else {
>>>> +                if( ! sps->state->in_map)
>>>> +                    fputc (' ', sps->stream);
>>>> +            }
>>>> +        } else {
>>>> +            sps->state->first = FALSE;
>>>> +        }
>>>> +    }
>>>> +    return sps;
>>>> +}
>>>> +
>>>> +/* Helper function to begin an aggregate type.  Prints the open
>>>> + * character and pushes a new state frame. */
>>>> +static void
>>>> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
>>>> +{
>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>> +    struct sexp_state *state = talloc (sps, struct sexp_state);
>>>> +    fputc (open, sps->stream);
>>>> +    state->parent = sps->state;
>>>> +    state->first = TRUE;
>>>> +    state->in_map = FALSE;
>>>> +    state->close = close;
>>>> +    sps->state = state;
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_begin_map (struct sprinter *sp)
>>>> +{
>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>> +    sexp_begin_aggregate (sp, '(', ')');
>>>> +    sps->state->in_map = TRUE;
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_begin_list (struct sprinter *sp)
>>>> +{
>>>> +    sexp_begin_aggregate (sp, '(', ')');
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_end (struct sprinter *sp)
>>>> +{
>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>> +    struct sexp_state *state = sps->state;
>>>> +
>>>> +    if (sps->state->in_map)
>>>> +        fputc (')', sps->stream);
>>>> +    fputc (sps->state->close, sps->stream);
>>>> +    sps->state = state->parent;
>>>> +    talloc_free (state);
>>>> +    if (sps->state == NULL)
>>>> +        fputc ('\n', sps->stream);
>>>> +}
>>>> +
>>>> +/* This implementation supports embedded NULs as allowed by the JSON
>>>> + * specification and Unicode.  Support for *parsing* embedded NULs
>>>> + * varies, but is generally not a problem outside of C-based parsers
>>>> + * (Python's json module and Emacs' json.el take embedded NULs in
>>>> + * stride). */
>>>> +static void
>>>> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
>>>> +{
>>>> +    static const char *const escapes[] = {
>>>> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
>>>> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
>>>> +    };
>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>> +
>>>> +    if(quote)
>>>> +        fputc ('"', sps->stream);
>>>> +    for (; len; ++val, --len) {
>>>> +        unsigned char ch = *val;
>>>> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
>>>> +            fputs (escapes[ch], sps->stream);
>>>> +        else if (ch >= 32)
>>>> +            fputc (ch, sps->stream);
>>>> +        else
>>>> +            fprintf (sps->stream, "\\u%04x", ch);
>>>> +    }
>>>> +    if(quote)
>>>> +        fputc ('"', sps->stream);
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
>>>> +{
>>>> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
>>>> +{
>>>> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_string (struct sprinter *sp, const char *val)
>>>> +{
>>>> +    if (val == NULL)
>>>> +        val = "";
>>>> +    sexp_string_len (sp, val, strlen (val));
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_symbol (struct sprinter *sp, const char *val)
>>>> +{
>>>> +    if (val == NULL)
>>>> +        val = "";
>>>> +    sexp_symbol_len (sp, val, strlen (val));
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_integer (struct sprinter *sp, int val)
>>>> +{
>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>> +
>>>> +    fprintf (sps->stream, "%d", val);
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
>>>> +{
>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>> +
>>>> +    fputs (val ? "t" : "nil", sps->stream);
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_null (struct sprinter *sp)
>>>> +{
>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>> +
>>>> +    fputs ("nil", sps->stream);
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_map_key (struct sprinter *sp, const char *key)
>>>> +{
>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>> +
>>>> +    if( sps->state->in_map && ! sps->state->first)
>>>> +        fputs (") ", sps->stream);
>>>> +    fputc ('(', sps->stream);
>>>> +    sexp_symbol (sp, key);
>>>> +    fputc (' ', sps->stream);
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
>>>> +{
>>>> +}
>>>> +
>>>> +static void
>>>> +sexp_separator (struct sprinter *sp)
>>>> +{
>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>> +
>>>> +    sps->insert_separator = TRUE;
>>>> +}
>>>> +
>>>> +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,
>>>> +            .string_len = sexp_string_len,
>>>> +            .integer = sexp_integer,
>>>> +            .boolean = sexp_boolean,
>>>> +            .null = sexp_null,
>>>> +            .map_key = sexp_map_key,
>>>> +            .separator = sexp_separator,
>>>> +            .set_prefix = sexp_set_prefix,
>>>> +            .is_text_printer = FALSE,
>>>> +        }
>>>> +    };
>>>> +    struct sprinter_sexp *res;
>>>> +
>>>> +    res = talloc (ctx, struct sprinter_sexp);
>>>> +    if (! res)
>>>> +        return NULL;
>>>> +
>>>> +    *res = template;
>>>> +    res->stream = stream;
>>>> +    return &res->vtable;
>>>> +}
>>>> -- 
>>>> 1.8.0
>>>>
>>>> _______________________________________________
>>>> notmuch mailing list
>>>> notmuch@notmuchmail.org
>>>> http://notmuchmail.org/mailman/listinfo/notmuch
>>> _______________________________________________
>>> notmuch mailing list
>>> notmuch@notmuchmail.org
>>> http://notmuchmail.org/mailman/listinfo/notmuch
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01 13:29       ` Tomi Ollila
@ 2012-12-01 13:45         ` Mark Walters
  2012-12-01 14:26           ` Mark Walters
  0 siblings, 1 reply; 38+ messages in thread
From: Mark Walters @ 2012-12-01 13:45 UTC (permalink / raw)
  To: Tomi Ollila, Peter Feigl, notmuch

On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
> On Sat, Dec 01 2012, Mark Walters <markwalters1009@gmail.com> wrote:
>
>> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>>> On Sat, Dec 01 2012, Mark Walters wrote:
>>>
>>>> Hi
>>>>
>>>> Overall I like the series: I think I agree with all of Jani's
>>>> comments. 
>>>>
>>>> My one extra comment is that I think we should decide on whether we also
>>>> want a sexp plist version. I think we might want one for the emacs
>>>> front-end as that currently uses plists for everything.
>>>>
>>>> If we do we might want to change the names a little, both for functions
>>>> and options (eg sexp_a and sexp_p or something). Probably a lot of
>>>> sprinter-sexp would be common to both versions.
>>>
>>> This is an important question that needs to be addressed fast: options
>>> are:
>>>
>>> 1) have options to spit both alist & plist formats
>>> 2) when converting emacs to use s-expressions, convert it to use alists
>>> 3) start using plists instead of alists in Peter's android client
>>
>> Ok I have looked at this and the changes needed to output plist (or
>> both) are pretty small: the only functions from sprinter-sexp.c that
>> need to be changed are sexp_end and sexp_map_key. The total diff from
>> alist to plist is about 10 lines. I have a version which allows both
>> (the same sprinter file creates both possibilities) and have hooked it
>> into emacs/notmuch-show.el and it all seems to work. 
>>
>> (Search is more difficult as that uses the async parser; indeed even for
>> show I used sexp-at-point as suggested by Tomi which seems rather
>> underdocumented but does seem to work)
>>
>> Given the ease with which we can allow both I think that would be my
>> preference: the biggest problem is that slightly more cluttered option
>> list (i.e., we have to allow both --format=sexpa and --format=sexpp or
>> similar).
>>
>> (I can post the patch doing the above but almost all of it is modifying the
>> commands to choose alist or plist rather than modifying the
>> sprinter-sexp itself)
>
> As the diff is so small I agree that supporting 2 formats is good
> option.
>
> In case this is done I suggest that we proceed the following way:
>
> 1) Agree how we call these formats (sexpa & sexpp or something else)
> 2) Peter does his updates, including to call the format as will be decided
> 3) Mark posts his patches after Peter's work is pushed

I think this seems a good approach. I should note that while the diff
between alist and plist is only about 10 lines I do need to duplicate
some of the surrounding code to allow both: its not huge but definitely
more than 10 lines. 

I will clean it up a little and then post (as a diff on top of Peter's
patches) just so people can judge if it looks basically acceptable.

Best wishes 

Mark



>
>>
>> Best wishes
>>
>> Mark
>
> Tomi
>
>
>>
>>
>>
>>
>>
>>>
>>>
>>> In case (1) is chosen then we just need to support one more format.
>>>
>>> How much work would it involve to convert emacs to receive content in
>>> alists (and how feasible would that be)?
>>>
>>> How much work would it require in Peter's client to use plists (and how
>>> feasible would that be)?
>>>
>>>>
>>>> Best wishes
>>>>
>>>> Mark
>>>
>>> Tomi
>>>
>>>
>>>>
>>>>
>>>> On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
>>>>> This commit adds an sprinter for Lisp S-Expressions. Later commits will
>>>>> use this printer.
>>>>>
>>>>> The structure is the same as json, but:
>>>>> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
>>>>> - maps are written as a-lists: ((key "value") (other-key "other-value"))
>>>>> - true is written as t
>>>>> - false is written as nil
>>>>> - null is written as nil
>>>>> ---
>>>>>  Makefile.local  |   1 +
>>>>>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>>>  2 files changed, 236 insertions(+)
>>>>>  create mode 100644 sprinter-sexp.c
>>>>>
>>>>> diff --git a/Makefile.local b/Makefile.local
>>>>> index 2b91946..0db1713 100644
>>>>> --- a/Makefile.local
>>>>> +++ b/Makefile.local
>>>>> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>>>>>  	notmuch-tag.c		\
>>>>>  	notmuch-time.c		\
>>>>>  	sprinter-json.c		\
>>>>> +	sprinter-sexp.c		\
>>>>>  	sprinter-text.c		\
>>>>>  	query-string.c		\
>>>>>  	mime-node.c		\
>>>>> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
>>>>> new file mode 100644
>>>>> index 0000000..8401c52
>>>>> --- /dev/null
>>>>> +++ b/sprinter-sexp.c
>>>>> @@ -0,0 +1,235 @@
>>>>> +#include <stdbool.h>
>>>>> +#include <stdio.h>
>>>>> +#include <talloc.h>
>>>>> +#include "sprinter.h"
>>>>> +
>>>>> +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;
>>>>> +
>>>>> +    /* A flag to signify that a separator should be inserted in the
>>>>> +     * output as soon as possible.
>>>>> +     */
>>>>> +    notmuch_bool_t insert_separator;
>>>>> +};
>>>>> +
>>>>> +struct sexp_state {
>>>>> +    struct sexp_state *parent;
>>>>> +
>>>>> +    /* True if nothing has been printed in this aggregate yet.
>>>>> +     * Suppresses the space before a value. */
>>>>> +    notmuch_bool_t first;
>>>>> +
>>>>> +    /* True if the state is a map state.
>>>>> +       Used to add a space between key/value pairs. */
>>>>> +    notmuch_bool_t in_map;
>>>>> +
>>>>> +    /* The character that closes the current aggregate. */
>>>>> +    char close;
>>>>> +};
>>>>> +
>>>>> +/* Helper function to set up the stream to print a value.  If this
>>>>> + * value follows another value, prints a space. */
>>>>> +static struct sprinter_sexp *
>>>>> +sexp_begin_value (struct sprinter *sp)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>>> +
>>>>> +    if (sps->state) {
>>>>> +        if (! sps->state->first) {
>>>>> +            if (sps->insert_separator) {
>>>>> +                fputc ('\n', sps->stream);
>>>>> +                sps->insert_separator = FALSE;
>>>>> +            } else {
>>>>> +                if( ! sps->state->in_map)
>>>>> +                    fputc (' ', sps->stream);
>>>>> +            }
>>>>> +        } else {
>>>>> +            sps->state->first = FALSE;
>>>>> +        }
>>>>> +    }
>>>>> +    return sps;
>>>>> +}
>>>>> +
>>>>> +/* Helper function to begin an aggregate type.  Prints the open
>>>>> + * character and pushes a new state frame. */
>>>>> +static void
>>>>> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>>> +    struct sexp_state *state = talloc (sps, struct sexp_state);
>>>>> +    fputc (open, sps->stream);
>>>>> +    state->parent = sps->state;
>>>>> +    state->first = TRUE;
>>>>> +    state->in_map = FALSE;
>>>>> +    state->close = close;
>>>>> +    sps->state = state;
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_begin_map (struct sprinter *sp)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>>> +    sexp_begin_aggregate (sp, '(', ')');
>>>>> +    sps->state->in_map = TRUE;
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_begin_list (struct sprinter *sp)
>>>>> +{
>>>>> +    sexp_begin_aggregate (sp, '(', ')');
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_end (struct sprinter *sp)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>>> +    struct sexp_state *state = sps->state;
>>>>> +
>>>>> +    if (sps->state->in_map)
>>>>> +        fputc (')', sps->stream);
>>>>> +    fputc (sps->state->close, sps->stream);
>>>>> +    sps->state = state->parent;
>>>>> +    talloc_free (state);
>>>>> +    if (sps->state == NULL)
>>>>> +        fputc ('\n', sps->stream);
>>>>> +}
>>>>> +
>>>>> +/* This implementation supports embedded NULs as allowed by the JSON
>>>>> + * specification and Unicode.  Support for *parsing* embedded NULs
>>>>> + * varies, but is generally not a problem outside of C-based parsers
>>>>> + * (Python's json module and Emacs' json.el take embedded NULs in
>>>>> + * stride). */
>>>>> +static void
>>>>> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
>>>>> +{
>>>>> +    static const char *const escapes[] = {
>>>>> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
>>>>> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
>>>>> +    };
>>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>>> +
>>>>> +    if(quote)
>>>>> +        fputc ('"', sps->stream);
>>>>> +    for (; len; ++val, --len) {
>>>>> +        unsigned char ch = *val;
>>>>> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
>>>>> +            fputs (escapes[ch], sps->stream);
>>>>> +        else if (ch >= 32)
>>>>> +            fputc (ch, sps->stream);
>>>>> +        else
>>>>> +            fprintf (sps->stream, "\\u%04x", ch);
>>>>> +    }
>>>>> +    if(quote)
>>>>> +        fputc ('"', sps->stream);
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
>>>>> +{
>>>>> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
>>>>> +{
>>>>> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_string (struct sprinter *sp, const char *val)
>>>>> +{
>>>>> +    if (val == NULL)
>>>>> +        val = "";
>>>>> +    sexp_string_len (sp, val, strlen (val));
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_symbol (struct sprinter *sp, const char *val)
>>>>> +{
>>>>> +    if (val == NULL)
>>>>> +        val = "";
>>>>> +    sexp_symbol_len (sp, val, strlen (val));
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_integer (struct sprinter *sp, int val)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>>> +
>>>>> +    fprintf (sps->stream, "%d", val);
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>>> +
>>>>> +    fputs (val ? "t" : "nil", sps->stream);
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_null (struct sprinter *sp)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
>>>>> +
>>>>> +    fputs ("nil", sps->stream);
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_map_key (struct sprinter *sp, const char *key)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>>> +
>>>>> +    if( sps->state->in_map && ! sps->state->first)
>>>>> +        fputs (") ", sps->stream);
>>>>> +    fputc ('(', sps->stream);
>>>>> +    sexp_symbol (sp, key);
>>>>> +    fputc (' ', sps->stream);
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
>>>>> +{
>>>>> +}
>>>>> +
>>>>> +static void
>>>>> +sexp_separator (struct sprinter *sp)
>>>>> +{
>>>>> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
>>>>> +
>>>>> +    sps->insert_separator = TRUE;
>>>>> +}
>>>>> +
>>>>> +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,
>>>>> +            .string_len = sexp_string_len,
>>>>> +            .integer = sexp_integer,
>>>>> +            .boolean = sexp_boolean,
>>>>> +            .null = sexp_null,
>>>>> +            .map_key = sexp_map_key,
>>>>> +            .separator = sexp_separator,
>>>>> +            .set_prefix = sexp_set_prefix,
>>>>> +            .is_text_printer = FALSE,
>>>>> +        }
>>>>> +    };
>>>>> +    struct sprinter_sexp *res;
>>>>> +
>>>>> +    res = talloc (ctx, struct sprinter_sexp);
>>>>> +    if (! res)
>>>>> +        return NULL;
>>>>> +
>>>>> +    *res = template;
>>>>> +    res->stream = stream;
>>>>> +    return &res->vtable;
>>>>> +}
>>>>> -- 
>>>>> 1.8.0
>>>>>
>>>>> _______________________________________________
>>>>> notmuch mailing list
>>>>> notmuch@notmuchmail.org
>>>>> http://notmuchmail.org/mailman/listinfo/notmuch
>>>> _______________________________________________
>>>> notmuch mailing list
>>>> notmuch@notmuchmail.org
>>>> http://notmuchmail.org/mailman/listinfo/notmuch
>> _______________________________________________
>> notmuch mailing list
>> notmuch@notmuchmail.org
>> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01 13:45         ` Mark Walters
@ 2012-12-01 14:26           ` Mark Walters
  2012-12-01 15:46             ` Mark Walters
  0 siblings, 1 reply; 38+ messages in thread
From: Mark Walters @ 2012-12-01 14:26 UTC (permalink / raw)
  To: Tomi Ollila, Peter Feigl, notmuch

On Sat, 01 Dec 2012, Mark Walters <markwalters1009@gmail.com> wrote:
> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>> On Sat, Dec 01 2012, Mark Walters <markwalters1009@gmail.com> wrote:
>>
>>> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>>>> On Sat, Dec 01 2012, Mark Walters wrote:
>>>>
>>>>> Hi
>>>>>
>>>>> Overall I like the series: I think I agree with all of Jani's
>>>>> comments. 
>>>>>
>>>>> My one extra comment is that I think we should decide on whether we also
>>>>> want a sexp plist version. I think we might want one for the emacs
>>>>> front-end as that currently uses plists for everything.
>>>>>
>>>>> If we do we might want to change the names a little, both for functions
>>>>> and options (eg sexp_a and sexp_p or something). Probably a lot of
>>>>> sprinter-sexp would be common to both versions.
>>>>
>>>> This is an important question that needs to be addressed fast: options
>>>> are:
>>>>
>>>> 1) have options to spit both alist & plist formats
>>>> 2) when converting emacs to use s-expressions, convert it to use alists
>>>> 3) start using plists instead of alists in Peter's android client
>>>
>>> Ok I have looked at this and the changes needed to output plist (or
>>> both) are pretty small: the only functions from sprinter-sexp.c that
>>> need to be changed are sexp_end and sexp_map_key. The total diff from
>>> alist to plist is about 10 lines. I have a version which allows both
>>> (the same sprinter file creates both possibilities) and have hooked it
>>> into emacs/notmuch-show.el and it all seems to work. 
>>>
>>> (Search is more difficult as that uses the async parser; indeed even for
>>> show I used sexp-at-point as suggested by Tomi which seems rather
>>> underdocumented but does seem to work)
>>>
>>> Given the ease with which we can allow both I think that would be my
>>> preference: the biggest problem is that slightly more cluttered option
>>> list (i.e., we have to allow both --format=sexpa and --format=sexpp or
>>> similar).
>>>
>>> (I can post the patch doing the above but almost all of it is modifying the
>>> commands to choose alist or plist rather than modifying the
>>> sprinter-sexp itself)
>>
>> As the diff is so small I agree that supporting 2 formats is good
>> option.
>>
>> In case this is done I suggest that we proceed the following way:
>>
>> 1) Agree how we call these formats (sexpa & sexpp or something else)
>> 2) Peter does his updates, including to call the format as will be decided
>> 3) Mark posts his patches after Peter's work is pushed
>
> I think this seems a good approach. I should note that while the diff
> between alist and plist is only about 10 lines I do need to duplicate
> some of the surrounding code to allow both: its not huge but definitely
> more than 10 lines. 
>
> I will clean it up a little and then post (as a diff on top of Peter's
> patches) just so people can judge if it looks basically acceptable.

Here is my current diff: note this is just the sprinter diff: it does
not contain the plumbing into the cli functions. The largest part is the
duplicated create function: I couldn't persuade the compiler to let me
unify this (as the static const thing needs to be constant)

The patch is -U6 as this makes it easier to see what is going on.

Best wishes

Mark

---
 sprinter-sexp.c |   75 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 sprinter.h      |    6 +++-
 2 files changed, 73 insertions(+), 8 deletions(-)

diff --git a/sprinter-sexp.c b/sprinter-sexp.c
index 8401c52..51bab68 100644
--- a/sprinter-sexp.c
+++ b/sprinter-sexp.c
@@ -1,11 +1,16 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <talloc.h>
 #include "sprinter.h"
 
+typedef enum {
+    SEXP_ALIST,
+    SEXP_PLIST
+} sexp_list_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;
@@ -81,26 +86,39 @@ static void
 sexp_begin_list (struct sprinter *sp)
 {
     sexp_begin_aggregate (sp, '(', ')');
 }
 
 static void
-sexp_end (struct sprinter *sp)
+sexp_end (struct sprinter *sp, sexp_list_t list_type)
 {
     struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
     struct sexp_state *state = sps->state;
 
-    if (sps->state->in_map)
-        fputc (')', sps->stream);
+    if (list_type == SEXP_ALIST)
+	if (sps->state->in_map)
+	    fputc (' ', sps->stream);
     fputc (sps->state->close, sps->stream);
     sps->state = state->parent;
     talloc_free (state);
     if (sps->state == NULL)
         fputc ('\n', sps->stream);
 }
 
+static void
+sexp_end_alist (struct sprinter *sp)
+{
+    sexp_end(sp, SEXP_ALIST);
+}
+
+static void
+sexp_end_plist (struct sprinter *sp)
+{
+    sexp_end(sp, SEXP_PLIST);
+}
+
 /* This implementation supports embedded NULs as allowed by the JSON
  * specification and Unicode.  Support for *parsing* embedded NULs
  * varies, but is generally not a problem outside of C-based parsers
  * (Python's json module and Emacs' json.el take embedded NULs in
  * stride). */
 static void
@@ -177,24 +195,36 @@ sexp_null (struct sprinter *sp)
     struct sprinter_sexp *sps = sexp_begin_value (sp);
 
     fputs ("nil", sps->stream);
 }
 
 static void
-sexp_map_key (struct sprinter *sp, const char *key)
+sexp_map_key_alist (struct sprinter *sp, const char *key)
 {
     struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
 
     if( sps->state->in_map && ! sps->state->first)
         fputs (") ", sps->stream);
     fputc ('(', sps->stream);
     sexp_symbol (sp, key);
     fputc (' ', sps->stream);
 }
 
 static void
+sexp_map_key_plist (struct sprinter *sp, const char *key)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if( sps->state->in_map && ! sps->state->first)
+        fputs (" ", sps->stream);
+    fputc (':', sps->stream);
+    sexp_symbol (sp, key);
+    fputc (' ', sps->stream);
+}
+
+static void
 sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
 {
 }
 
 static void
 sexp_separator (struct sprinter *sp)
@@ -202,25 +232,56 @@ sexp_separator (struct sprinter *sp)
     struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
 
     sps->insert_separator = TRUE;
 }
 
 struct sprinter *
-sprinter_sexp_create (const void *ctx, FILE *stream)
+sprinter_sexp_alist_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_alist,
+            .string = sexp_string,
+            .string_len = sexp_string_len,
+            .integer = sexp_integer,
+            .boolean = sexp_boolean,
+            .null = sexp_null,
+            .map_key = sexp_map_key_alist,
+            .separator = sexp_separator,
+            .set_prefix = sexp_set_prefix,
+            .is_text_printer = FALSE,
+        }
+    };
+    struct sprinter_sexp *res;
+
+    res = talloc (ctx, struct sprinter_sexp);
+    if (! res)
+        return NULL;
+
+    *res = template;
+    res->stream = stream;
+    return &res->vtable;
+}
+
+
+struct sprinter *
+sprinter_sexp_plist_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,
+            .end = sexp_end_plist,
             .string = sexp_string,
             .string_len = sexp_string_len,
             .integer = sexp_integer,
             .boolean = sexp_boolean,
             .null = sexp_null,
-            .map_key = sexp_map_key,
+            .map_key = sexp_map_key_plist,
             .separator = sexp_separator,
             .set_prefix = sexp_set_prefix,
             .is_text_printer = FALSE,
         }
     };
     struct sprinter_sexp *res;
diff --git a/sprinter.h b/sprinter.h
index 59776a9..9a496cd 100644
--- a/sprinter.h
+++ b/sprinter.h
@@ -69,9 +69,13 @@ sprinter_text_create (const void *ctx, FILE *stream);
 /* Create a new structure printer that emits JSON. */
 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);
+sprinter_sexp_alist_create (const void *ctx, FILE *stream);
+
+/* Create a new structure printer that emits S-Expressions. */
+struct sprinter *
+sprinter_sexp_plist_create (const void *ctx, FILE *stream);
 
 #endif // NOTMUCH_SPRINTER_H
-- 
1.7.9.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01 12:24     ` Mark Walters
  2012-12-01 13:29       ` Tomi Ollila
@ 2012-12-01 15:18       ` Austin Clements
  1 sibling, 0 replies; 38+ messages in thread
From: Austin Clements @ 2012-12-01 15:18 UTC (permalink / raw)
  To: Mark Walters; +Cc: Tomi Ollila, notmuch

Quoth Mark Walters on Dec 01 at 12:24 pm:
> 
> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
> > On Sat, Dec 01 2012, Mark Walters wrote:
> >
> >> Hi
> >>
> >> Overall I like the series: I think I agree with all of Jani's
> >> comments. 
> >>
> >> My one extra comment is that I think we should decide on whether we also
> >> want a sexp plist version. I think we might want one for the emacs
> >> front-end as that currently uses plists for everything.
> >>
> >> If we do we might want to change the names a little, both for functions
> >> and options (eg sexp_a and sexp_p or something). Probably a lot of
> >> sprinter-sexp would be common to both versions.
> >
> > This is an important question that needs to be addressed fast: options
> > are:
> >
> > 1) have options to spit both alist & plist formats
> > 2) when converting emacs to use s-expressions, convert it to use alists
> > 3) start using plists instead of alists in Peter's android client
> 
> Ok I have looked at this and the changes needed to output plist (or
> both) are pretty small: the only functions from sprinter-sexp.c that
> need to be changed are sexp_end and sexp_map_key. The total diff from
> alist to plist is about 10 lines. I have a version which allows both
> (the same sprinter file creates both possibilities) and have hooked it
> into emacs/notmuch-show.el and it all seems to work. 
> 
> (Search is more difficult as that uses the async parser; indeed even for
> show I used sexp-at-point as suggested by Tomi which seems rather
> underdocumented but does seem to work)

I'm happy to write an async sexp parser.  I specifically designed the
async JSON parser with later switching to sexps in mind, so I already
have a plan for how to do this.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-12-01 14:26           ` Mark Walters
@ 2012-12-01 15:46             ` Mark Walters
  0 siblings, 0 replies; 38+ messages in thread
From: Mark Walters @ 2012-12-01 15:46 UTC (permalink / raw)
  To: Tomi Ollila, Peter Feigl, notmuch

On Sat, 01 Dec 2012, Mark Walters <markwalters1009@gmail.com> wrote:
> On Sat, 01 Dec 2012, Mark Walters <markwalters1009@gmail.com> wrote:
>> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>>> On Sat, Dec 01 2012, Mark Walters <markwalters1009@gmail.com> wrote:
>>>
>>>> On Sat, 01 Dec 2012, Tomi Ollila <tomi.ollila@iki.fi> wrote:
>>>>> On Sat, Dec 01 2012, Mark Walters wrote:
>>>>>
>>>>>> Hi
>>>>>>
>>>>>> Overall I like the series: I think I agree with all of Jani's
>>>>>> comments. 
>>>>>>
>>>>>> My one extra comment is that I think we should decide on whether we also
>>>>>> want a sexp plist version. I think we might want one for the emacs
>>>>>> front-end as that currently uses plists for everything.
>>>>>>
>>>>>> If we do we might want to change the names a little, both for functions
>>>>>> and options (eg sexp_a and sexp_p or something). Probably a lot of
>>>>>> sprinter-sexp would be common to both versions.
>>>>>
>>>>> This is an important question that needs to be addressed fast: options
>>>>> are:
>>>>>
>>>>> 1) have options to spit both alist & plist formats
>>>>> 2) when converting emacs to use s-expressions, convert it to use alists
>>>>> 3) start using plists instead of alists in Peter's android client
>>>>
>>>> Ok I have looked at this and the changes needed to output plist (or
>>>> both) are pretty small: the only functions from sprinter-sexp.c that
>>>> need to be changed are sexp_end and sexp_map_key. The total diff from
>>>> alist to plist is about 10 lines. I have a version which allows both
>>>> (the same sprinter file creates both possibilities) and have hooked it
>>>> into emacs/notmuch-show.el and it all seems to work. 
>>>>
>>>> (Search is more difficult as that uses the async parser; indeed even for
>>>> show I used sexp-at-point as suggested by Tomi which seems rather
>>>> underdocumented but does seem to work)
>>>>
>>>> Given the ease with which we can allow both I think that would be my
>>>> preference: the biggest problem is that slightly more cluttered option
>>>> list (i.e., we have to allow both --format=sexpa and --format=sexpp or
>>>> similar).
>>>>
>>>> (I can post the patch doing the above but almost all of it is modifying the
>>>> commands to choose alist or plist rather than modifying the
>>>> sprinter-sexp itself)
>>>
>>> As the diff is so small I agree that supporting 2 formats is good
>>> option.
>>>
>>> In case this is done I suggest that we proceed the following way:
>>>
>>> 1) Agree how we call these formats (sexpa & sexpp or something else)
>>> 2) Peter does his updates, including to call the format as will be decided
>>> 3) Mark posts his patches after Peter's work is pushed
>>
>> I think this seems a good approach. I should note that while the diff
>> between alist and plist is only about 10 lines I do need to duplicate
>> some of the surrounding code to allow both: its not huge but definitely
>> more than 10 lines. 
>>
>> I will clean it up a little and then post (as a diff on top of Peter's
>> patches) just so people can judge if it looks basically acceptable.
>
> Here is my current diff: note this is just the sprinter diff: it does
> not contain the plumbing into the cli functions. The largest part is the
> duplicated create function: I couldn't persuade the compiler to let me
> unify this (as the static const thing needs to be constant)
>
> The patch is -U6 as this makes it easier to see what is going on.

At Tomi's suggestion (on irc) here is a much nicer version of the change
needed for alist/plist.

Best wishes

Mark

---
 sprinter-sexp.c |   41 ++++++++++++++++++++++++++++++++++-------
 sprinter.h      |    6 +++++-
 2 files changed, 39 insertions(+), 8 deletions(-)

diff --git a/sprinter-sexp.c b/sprinter-sexp.c
index 8401c52..90ec9a0 100644
--- a/sprinter-sexp.c
+++ b/sprinter-sexp.c
@@ -3,6 +3,11 @@
 #include <talloc.h>
 #include "sprinter.h"
 
+typedef enum {
+    SEXP_ALIST,
+    SEXP_PLIST
+} sexp_list_t;
+
 struct sprinter_sexp {
     struct sprinter vtable;
     FILE *stream;
@@ -10,6 +15,8 @@ struct sprinter_sexp {
      * inside any aggregate types. */
     struct sexp_state *state;
 
+    /* A flag to say if the sprinter print alists or plists. */
+    sexp_list_t list_type;
     /* A flag to signify that a separator should be inserted in the
      * output as soon as possible.
      */
@@ -89,8 +96,9 @@ sexp_end (struct sprinter *sp)
     struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
     struct sexp_state *state = sps->state;
 
-    if (sps->state->in_map)
-        fputc (')', sps->stream);
+    if (sps->list_type == SEXP_ALIST)
+	if (sps->state->in_map)
+	    fputc (' ', sps->stream);
     fputc (sps->state->close, sps->stream);
     sps->state = state->parent;
     talloc_free (state);
@@ -184,9 +192,15 @@ sexp_map_key (struct sprinter *sp, const char *key)
 {
     struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
 
-    if( sps->state->in_map && ! sps->state->first)
-        fputs (") ", sps->stream);
-    fputc ('(', sps->stream);
+    if (sps->list_type == SEXP_ALIST) {
+	if( sps->state->in_map && ! sps->state->first)
+	    fputs (") ", sps->stream);
+	fputc ('(', sps->stream);
+    } else {
+	if( sps->state->in_map && ! sps->state->first)
+	    fputs (" ", sps->stream);
+	fputc (':', sps->stream);
+    }
     sexp_symbol (sp, key);
     fputc (' ', sps->stream);
 }
@@ -204,8 +218,8 @@ sexp_separator (struct sprinter *sp)
     sps->insert_separator = TRUE;
 }
 
-struct sprinter *
-sprinter_sexp_create (const void *ctx, FILE *stream)
+static struct sprinter *
+sprinter_sexp_create (const void *ctx, FILE *stream, sexp_list_t list_type)
 {
     static const struct sprinter_sexp template = {
         .vtable = {
@@ -231,5 +245,18 @@ sprinter_sexp_create (const void *ctx, FILE *stream)
 
     *res = template;
     res->stream = stream;
+    res->list_type = list_type;
     return &res->vtable;
 }
+
+struct sprinter *
+sprinter_sexp_alist_create (const void *ctx, FILE *stream)
+{
+    return sprinter_sexp_create (ctx, stream, SEXP_ALIST);
+}
+
+struct sprinter *
+sprinter_sexp_plist_create (const void *ctx, FILE *stream)
+{
+    return sprinter_sexp_create (ctx, stream, SEXP_PLIST);
+}
diff --git a/sprinter.h b/sprinter.h
index 59776a9..9a496cd 100644
--- a/sprinter.h
+++ b/sprinter.h
@@ -72,6 +72,10 @@ 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);
+sprinter_sexp_alist_create (const void *ctx, FILE *stream);
+
+/* Create a new structure printer that emits S-Expressions. */
+struct sprinter *
+sprinter_sexp_plist_create (const void *ctx, FILE *stream);
 
 #endif // NOTMUCH_SPRINTER_H
-- 
1.7.9.1

 

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/3] Adding an S-expression structured output printer.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (5 preceding siblings ...)
  2012-12-01  9:59 ` Mark Walters
@ 2012-12-01 16:14 ` Mark Walters
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 38+ messages in thread
From: Mark Walters @ 2012-12-01 16:14 UTC (permalink / raw)
  To: Peter Feigl, notmuch


One small niggle with this patch: it seems to have lots of spaces rather
than tabs (unless I am misreading it)

Best wishes

Mark

On Fri, 30 Nov 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit adds an sprinter for Lisp S-Expressions. Later commits will
> use this printer.
>
> The structure is the same as json, but:
> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
> - maps are written as a-lists: ((key "value") (other-key "other-value"))
> - true is written as t
> - false is written as nil
> - null is written as nil
> ---
>  Makefile.local  |   1 +
>  sprinter-sexp.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 236 insertions(+)
>  create mode 100644 sprinter-sexp.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 2b91946..0db1713 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>  	notmuch-tag.c		\
>  	notmuch-time.c		\
>  	sprinter-json.c		\
> +	sprinter-sexp.c		\
>  	sprinter-text.c		\
>  	query-string.c		\
>  	mime-node.c		\
> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
> new file mode 100644
> index 0000000..8401c52
> --- /dev/null
> +++ b/sprinter-sexp.c
> @@ -0,0 +1,235 @@
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <talloc.h>
> +#include "sprinter.h"
> +
> +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;
> +
> +    /* A flag to signify that a separator should be inserted in the
> +     * output as soon as possible.
> +     */
> +    notmuch_bool_t insert_separator;
> +};
> +
> +struct sexp_state {
> +    struct sexp_state *parent;
> +
> +    /* True if nothing has been printed in this aggregate yet.
> +     * Suppresses the space before a value. */
> +    notmuch_bool_t first;
> +
> +    /* True if the state is a map state.
> +       Used to add a space between key/value pairs. */
> +    notmuch_bool_t in_map;
> +
> +    /* The character that closes the current aggregate. */
> +    char close;
> +};
> +
> +/* Helper function to set up the stream to print a value.  If this
> + * value follows another value, prints a space. */
> +static struct sprinter_sexp *
> +sexp_begin_value (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if (sps->state) {
> +        if (! sps->state->first) {
> +            if (sps->insert_separator) {
> +                fputc ('\n', sps->stream);
> +                sps->insert_separator = FALSE;
> +            } else {
> +                if( ! sps->state->in_map)
> +                    fputc (' ', sps->stream);
> +            }
> +        } else {
> +            sps->state->first = FALSE;
> +        }
> +    }
> +    return sps;
> +}
> +
> +/* Helper function to begin an aggregate type.  Prints the open
> + * character and pushes a new state frame. */
> +static void
> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +    struct sexp_state *state = talloc (sps, struct sexp_state);
> +    fputc (open, sps->stream);
> +    state->parent = sps->state;
> +    state->first = TRUE;
> +    state->in_map = FALSE;
> +    state->close = close;
> +    sps->state = state;
> +}
> +
> +static void
> +sexp_begin_map (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    sexp_begin_aggregate (sp, '(', ')');
> +    sps->state->in_map = TRUE;
> +}
> +
> +static void
> +sexp_begin_list (struct sprinter *sp)
> +{
> +    sexp_begin_aggregate (sp, '(', ')');
> +}
> +
> +static void
> +sexp_end (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    struct sexp_state *state = sps->state;
> +
> +    if (sps->state->in_map)
> +        fputc (')', sps->stream);
> +    fputc (sps->state->close, sps->stream);
> +    sps->state = state->parent;
> +    talloc_free (state);
> +    if (sps->state == NULL)
> +        fputc ('\n', sps->stream);
> +}
> +
> +/* This implementation supports embedded NULs as allowed by the JSON
> + * specification and Unicode.  Support for *parsing* embedded NULs
> + * varies, but is generally not a problem outside of C-based parsers
> + * (Python's json module and Emacs' json.el take embedded NULs in
> + * stride). */
> +static void
> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
> +{
> +    static const char *const escapes[] = {
> +        ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
> +        ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
> +    };
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    if(quote)
> +        fputc ('"', sps->stream);
> +    for (; len; ++val, --len) {
> +        unsigned char ch = *val;
> +        if (ch < ARRAY_SIZE (escapes) && escapes[ch])
> +            fputs (escapes[ch], sps->stream);
> +        else if (ch >= 32)
> +            fputc (ch, sps->stream);
> +        else
> +            fprintf (sps->stream, "\\u%04x", ch);
> +    }
> +    if(quote)
> +        fputc ('"', sps->stream);
> +}
> +
> +static void
> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
> +}
> +
> +static void
> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
> +}
> +
> +static void
> +sexp_string (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_string_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_symbol (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +        val = "";
> +    sexp_symbol_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_integer (struct sprinter *sp, int val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fprintf (sps->stream, "%d", val);
> +}
> +
> +static void
> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs (val ? "t" : "nil", sps->stream);
> +}
> +
> +static void
> +sexp_null (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs ("nil", sps->stream);
> +}
> +
> +static void
> +sexp_map_key (struct sprinter *sp, const char *key)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if( sps->state->in_map && ! sps->state->first)
> +        fputs (") ", sps->stream);
> +    fputc ('(', sps->stream);
> +    sexp_symbol (sp, key);
> +    fputc (' ', sps->stream);
> +}
> +
> +static void
> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
> +{
> +}
> +
> +static void
> +sexp_separator (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    sps->insert_separator = TRUE;
> +}
> +
> +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,
> +            .string_len = sexp_string_len,
> +            .integer = sexp_integer,
> +            .boolean = sexp_boolean,
> +            .null = sexp_null,
> +            .map_key = sexp_map_key,
> +            .separator = sexp_separator,
> +            .set_prefix = sexp_set_prefix,
> +            .is_text_printer = FALSE,
> +        }
> +    };
> +    struct sprinter_sexp *res;
> +
> +    res = talloc (ctx, struct sprinter_sexp);
> +    if (! res)
> +        return NULL;
> +
> +    *res = template;
> +    res->stream = stream;
> +    return &res->vtable;
> +}
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v2 0/5] New output format sexp (Lisp S-Expressions)
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (6 preceding siblings ...)
  2012-12-01 16:14 ` Mark Walters
@ 2012-12-04 14:46 ` Peter Feigl
  2012-12-04 17:07   ` Jani Nikula
                     ` (6 more replies)
  2012-12-04 14:46 ` [PATCH v2 1/5] Adding an S-expression structured output printer Peter Feigl
                   ` (4 subsequent siblings)
  12 siblings, 7 replies; 38+ messages in thread
From: Peter Feigl @ 2012-12-04 14:46 UTC (permalink / raw)
  To: notmuch

This patch series adds a new output format "sexp" to notmuch-reply,
notmuch-show and notmuch-search. These are useful for the Android mobile
client and perhaps other Lisp programs as well.
After the switch to a generic structured output printer, which was
committed some months ago, these patches just add another one (like the
json structured output printer).
Basic tests and updates to the man pages are also included.


Peter Feigl (5):
  Adding an S-expression structured output printer.
  Rename the -json printer functions in notmuch-reply and    
    notmuch-show to generic -sprinter functions.
  Use the S-Expression structured printer in notmuch-show,
    notmuch-reply     and notmuch-search.
  Adding tests for --format=sexp.
  Updating man pages for new S-Expression output format.

 Makefile.local            |   1 +
 man/man1/notmuch-reply.1  |  14 ++-
 man/man1/notmuch-search.1 |  15 +--
 man/man1/notmuch-show.1   |  36 +++++--
 notmuch-client.h          |   8 +-
 notmuch-reply.c           |  43 ++++----
 notmuch-search.c          |   6 +-
 notmuch-show.c            |  48 +++++----
 sprinter-sexp.c           | 250 ++++++++++++++++++++++++++++++++++++++++++++++
 sprinter.h                |   4 +
 test/notmuch-test         |   1 +
 test/sexp                 |  48 +++++++++
 12 files changed, 414 insertions(+), 60 deletions(-)
 create mode 100644 sprinter-sexp.c
 create mode 100755 test/sexp

-- 
1.8.0

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v2 1/5] Adding an S-expression structured output printer.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (7 preceding siblings ...)
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
@ 2012-12-04 14:46 ` Peter Feigl
  2012-12-04 19:14   ` Austin Clements
  2012-12-04 14:46 ` [PATCH v2 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
                   ` (3 subsequent siblings)
  12 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-12-04 14:46 UTC (permalink / raw)
  To: notmuch

This commit adds a structured output printer for Lisp
S-Expressions. Later commits will use this printer in notmuch search,
show and reply.

The structure is the same as json, but:
- arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
- maps are written as a-lists: ((key "value") (other-key "other-value"))
- true is written as t
- false is written as nil
- null is written as nil
---
 Makefile.local  |   1 +
 sprinter-sexp.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sprinter.h      |   4 +
 3 files changed, 255 insertions(+)
 create mode 100644 sprinter-sexp.c

diff --git a/Makefile.local b/Makefile.local
index 2b91946..0db1713 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -270,6 +270,7 @@ notmuch_client_srcs =		\
 	notmuch-tag.c		\
 	notmuch-time.c		\
 	sprinter-json.c		\
+	sprinter-sexp.c		\
 	sprinter-text.c		\
 	query-string.c		\
 	mime-node.c		\
diff --git a/sprinter-sexp.c b/sprinter-sexp.c
new file mode 100644
index 0000000..6d6bbad
--- /dev/null
+++ b/sprinter-sexp.c
@@ -0,0 +1,250 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2012 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+
+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;
+
+    /* A flag to signify that a separator should be inserted in the
+     * output as soon as possible.
+     */
+    notmuch_bool_t insert_separator;
+};
+
+struct sexp_state {
+    struct sexp_state *parent;
+
+    /* True if nothing has been printed in this aggregate yet.
+     * Suppresses the space before a value. */
+    notmuch_bool_t first;
+
+    /* True if the state is a map state.
+     * Used to add a space between key/value pairs. */
+    notmuch_bool_t in_map;
+
+    /* The character that closes the current aggregate. */
+    char close;
+};
+
+/* Helper function to set up the stream to print a value.  If this
+ * value follows another value, prints a space. */
+static struct sprinter_sexp *
+sexp_begin_value (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if (sps->state) {
+	if (! sps->state->first) {
+	    if (sps->insert_separator) {
+		fputc ('\n', sps->stream);
+		sps->insert_separator = FALSE;
+	    } else {
+		if (! sps->state->in_map)
+		    fputc (' ', sps->stream);
+	    }
+	} else {
+	    sps->state->first = FALSE;
+	}
+    }
+    return sps;
+}
+
+/* Helper function to begin an aggregate type.  Prints the open
+ * character and pushes a new state frame. */
+static void
+sexp_begin_aggregate (struct sprinter *sp, char open, char close)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+    struct sexp_state *state = talloc (sps, struct sexp_state);
+    fputc (open, sps->stream);
+    state->parent = sps->state;
+    state->first = TRUE;
+    state->in_map = FALSE;
+    state->close = close;
+    sps->state = state;
+}
+
+static void
+sexp_begin_map (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    sexp_begin_aggregate (sp, '(', ')');
+    sps->state->in_map = TRUE;
+}
+
+static void
+sexp_begin_list (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp, '(', ')');
+}
+
+static void
+sexp_end (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    struct sexp_state *state = sps->state;
+
+    if (sps->state->in_map)
+	fputc (')', sps->stream);
+    fputc (sps->state->close, sps->stream);
+    sps->state = state->parent;
+    talloc_free (state);
+    if (sps->state == NULL)
+	fputc ('\n', sps->stream);
+}
+
+static void
+sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
+{
+    static const char *const escapes[] = {
+	['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
+	['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
+    };
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    if(quote)
+	fputc ('"', sps->stream);
+    for (; len; ++val, --len) {
+	unsigned char ch = *val;
+	if (ch < ARRAY_SIZE (escapes) && escapes[ch])
+	    fputs (escapes[ch], sps->stream);
+	else if (ch >= 32)
+	    fputc (ch, sps->stream);
+	else
+	    fprintf (sps->stream, "\\u%04x", ch);
+    }
+    if(quote)
+	fputc ('"', sps->stream);
+}
+
+static void
+sexp_string_len (struct sprinter *sp, const char *val, size_t len)
+{
+    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
+}
+
+static void
+sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
+{
+    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */
+}
+
+static void
+sexp_string (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+	val = "";
+    sexp_string_len (sp, val, strlen (val));
+}
+
+static void
+sexp_symbol (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+	val = "";
+    sexp_symbol_len (sp, val, strlen (val));
+}
+
+static void
+sexp_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fprintf (sps->stream, "%d", val);
+}
+
+static void
+sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs (val ? "t" : "nil", sps->stream);
+}
+
+static void
+sexp_null (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs ("nil", sps->stream);
+}
+
+static void
+sexp_map_key (struct sprinter *sp, const char *key)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if (sps->state->in_map && ! sps->state->first)
+	fputs (") ", sps->stream);
+    fputc ('(', sps->stream);
+    sexp_symbol (sp, key);
+    fputc (' ', sps->stream);
+}
+
+static void
+sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
+{
+}
+
+static void
+sexp_separator (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    sps->insert_separator = TRUE;
+}
+
+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,
+	    .string_len = sexp_string_len,
+	    .integer = sexp_integer,
+	    .boolean = sexp_boolean,
+	    .null = sexp_null,
+	    .map_key = sexp_map_key,
+	    .separator = sexp_separator,
+	    .set_prefix = sexp_set_prefix,
+	    .is_text_printer = FALSE,
+	}
+    };
+    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 912a526..59776a9 100644
--- a/sprinter.h
+++ b/sprinter.h
@@ -70,4 +70,8 @@ sprinter_text_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.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v2 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (8 preceding siblings ...)
  2012-12-04 14:46 ` [PATCH v2 1/5] Adding an S-expression structured output printer Peter Feigl
@ 2012-12-04 14:46 ` Peter Feigl
  2012-12-04 19:18   ` Austin Clements
  2012-12-04 14:46 ` [PATCH v2 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
                   ` (2 subsequent siblings)
  12 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-12-04 14:46 UTC (permalink / raw)
  To: notmuch

All the structured output functions in notmuch-reply and notmuch-show
are renamed to a generic name (as they do not contain any json-specific
code anyway). This patch is a preparation to actually using the new
S-Expression sprinter in notmuch-reply and notmuch-show.
---
 notmuch-client.h |  8 ++++----
 notmuch-reply.c  | 38 +++++++++++++++++++++-----------------
 notmuch-show.c   | 30 +++++++++++++++---------------
 3 files changed, 40 insertions(+), 36 deletions(-)

diff --git a/notmuch-client.h b/notmuch-client.h
index ae9344b..1c336dc 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -175,12 +175,12 @@ notmuch_status_t
 show_one_part (const char *filename, int part);
 
 void
-format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node,
-		  notmuch_bool_t first, notmuch_bool_t output_body);
+format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
+		      notmuch_bool_t first, notmuch_bool_t output_body);
 
 void
-format_headers_json (struct sprinter *sp, GMimeMessage *message,
-		     notmuch_bool_t reply);
+format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
+			 notmuch_bool_t reply);
 
 typedef enum {
     NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
diff --git a/notmuch-reply.c b/notmuch-reply.c
index e60a264..53aefa7 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -548,7 +548,8 @@ notmuch_reply_format_default(void *ctx,
 			     notmuch_config_t *config,
 			     notmuch_query_t *query,
 			     notmuch_show_params_t *params,
-			     notmuch_bool_t reply_all)
+			     notmuch_bool_t reply_all,
+			     unused (sprinter_t *sp))
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
@@ -587,17 +588,17 @@ notmuch_reply_format_default(void *ctx,
 }
 
 static int
-notmuch_reply_format_json(void *ctx,
-			  notmuch_config_t *config,
-			  notmuch_query_t *query,
-			  notmuch_show_params_t *params,
-			  notmuch_bool_t reply_all)
+notmuch_reply_format_sprinter(void *ctx,
+			      notmuch_config_t *config,
+			      notmuch_query_t *query,
+			      notmuch_show_params_t *params,
+			      notmuch_bool_t reply_all,
+			      sprinter_t *sp)
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
     notmuch_message_t *message;
     mime_node_t *node;
-    sprinter_t *sp;
 
     if (notmuch_query_count_messages (query) != 1) {
 	fprintf (stderr, "Error: search term did not match precisely one message.\n");
@@ -613,18 +614,17 @@ notmuch_reply_format_json(void *ctx,
     if (!reply)
 	return 1;
 
-    sp = sprinter_json_create (ctx, stdout);
     sp->begin_map (sp);
 
     /* The headers of the reply message we've created */
     sp->map_key (sp, "reply-headers");
-    format_headers_json (sp, reply, TRUE);
+    format_headers_sprinter (sp, reply, TRUE);
     g_object_unref (G_OBJECT (reply));
     reply = NULL;
 
     /* Start the original */
     sp->map_key (sp, "original");
-    format_part_json (ctx, sp, node, TRUE, TRUE);
+    format_part_sprinter (ctx, sp, node, TRUE, TRUE);
 
     /* End */
     sp->end (sp);
@@ -639,7 +639,8 @@ notmuch_reply_format_headers_only(void *ctx,
 				  notmuch_config_t *config,
 				  notmuch_query_t *query,
 				  unused (notmuch_show_params_t *params),
-				  notmuch_bool_t reply_all)
+				  notmuch_bool_t reply_all,
+				  unused (sprinter_t *sp))
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
@@ -707,7 +708,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     notmuch_query_t *query;
     char *query_string;
     int opt_index, ret = 0;
-    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
+    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all, struct sprinter *sp);
     notmuch_show_params_t params = {
 	.part = -1,
 	.crypto = {
@@ -717,6 +718,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     };
     int format = FORMAT_DEFAULT;
     int reply_all = TRUE;
+    struct sprinter *sp = NULL;
 
     notmuch_opt_desc_t options[] = {
 	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
@@ -738,12 +740,14 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	return 1;
     }
 
-    if (format == FORMAT_HEADERS_ONLY)
+    if (format == FORMAT_HEADERS_ONLY) {
 	reply_format_func = notmuch_reply_format_headers_only;
-    else if (format == FORMAT_JSON)
-	reply_format_func = notmuch_reply_format_json;
-    else
+    } else if (format == FORMAT_JSON) {
+	reply_format_func = notmuch_reply_format_sprinter;
+	sp = sprinter_json_create (ctx, stdout);
+    } else {
 	reply_format_func = notmuch_reply_format_default;
+    }
 
     config = notmuch_config_open (ctx, NULL, NULL);
     if (config == NULL)
@@ -770,7 +774,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	return 1;
     }
 
-    if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
+    if (reply_format_func (ctx, config, query, &params, reply_all, sp) != 0)
 	return 1;
 
     notmuch_crypto_cleanup (&params.crypto);
diff --git a/notmuch-show.c b/notmuch-show.c
index 2fa2292..38c621f 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -32,12 +32,12 @@ static const notmuch_show_format_t format_text = {
 };
 
 static notmuch_status_t
-format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
 			int indent, const notmuch_show_params_t *params);
 
 static const notmuch_show_format_t format_json = {
     .new_sprinter = sprinter_json_create,
-    .part = format_part_json_entry,
+    .part = format_part_sprinter_entry,
 };
 
 static notmuch_status_t
@@ -108,7 +108,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
 /* Emit a sequence of key/value pairs for the metadata of message.
  * The caller should begin a map before calling this. */
 static void
-format_message_json (sprinter_t *sp, notmuch_message_t *message)
+format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
 {
     /* Any changes to the JSON format should be reflected in the file
      * devel/schemata. */
@@ -208,7 +208,7 @@ _is_from_line (const char *line)
 }
 
 void
-format_headers_json (sprinter_t *sp, GMimeMessage *message,
+format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
 		     notmuch_bool_t reply)
 {
     /* Any changes to the JSON format should be reflected in the file
@@ -363,7 +363,7 @@ signer_status_to_string (GMimeSignerStatus x)
 
 #ifdef GMIME_ATLEAST_26
 static void
-format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
     /* Any changes to the JSON format should be reflected in the file
      * devel/schemata. */
@@ -438,7 +438,7 @@ format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
 }
 #else
 static void
-format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
     const GMimeSignatureValidity* validity = node->sig_validity;
 
@@ -595,7 +595,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
 }
 
 void
-format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
+format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
 		  notmuch_bool_t first, notmuch_bool_t output_body)
 {
     /* Any changes to the JSON format should be reflected in the file
@@ -603,15 +603,15 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->envelope_file) {
 	sp->begin_map (sp);
-	format_message_json (sp, node->envelope_file);
+	format_message_sprinter (sp, node->envelope_file);
 
 	sp->map_key (sp, "headers");
-	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
+	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
 
 	if (output_body) {
 	    sp->map_key (sp, "body");
 	    sp->begin_list (sp);
-	    format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE);
+	    format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE);
 	    sp->end (sp);
 	}
 	sp->end (sp);
@@ -646,7 +646,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->verify_attempted) {
 	sp->map_key (sp, "sigstatus");
-	format_part_sigstatus_json (sp, node);
+	format_part_sigstatus_sprinter (sp, node);
     }
 
     sp->map_key (sp, "content-type");
@@ -698,7 +698,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 	sp->begin_map (sp);
 
 	sp->map_key (sp, "headers");
-	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
+	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
 
 	sp->map_key (sp, "body");
 	sp->begin_list (sp);
@@ -706,7 +706,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
     }
 
     for (i = 0; i < node->nchildren; i++)
-	format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
+	format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
 
     /* Close content structures */
     for (i = 0; i < nclose; i++)
@@ -716,11 +716,11 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 }
 
 static notmuch_status_t
-format_part_json_entry (const void *ctx, sprinter_t *sp,
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
 			mime_node_t *node, unused (int indent),
 			const notmuch_show_params_t *params)
 {
-    format_part_json (ctx, sp, node, TRUE, params->output_body);
+    format_part_sprinter (ctx, sp, node, TRUE, params->output_body);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v2 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (9 preceding siblings ...)
  2012-12-04 14:46 ` [PATCH v2 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
@ 2012-12-04 14:46 ` Peter Feigl
  2012-12-04 19:22   ` Austin Clements
  2012-12-04 14:46 ` [PATCH v2 4/5] Adding tests for --format=sexp Peter Feigl
  2012-12-04 14:46 ` [PATCH v2 5/5] Updating man pages for new S-Expression output format Peter Feigl
  12 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-12-04 14:46 UTC (permalink / raw)
  To: notmuch

This patch uses the new S-Expression printer in the notmuch CLI (show,
search and reply). You can now use --format=sexp for any of them.
---
 notmuch-reply.c  |  5 +++++
 notmuch-search.c |  6 +++++-
 notmuch-show.c   | 18 ++++++++++++++----
 3 files changed, 24 insertions(+), 5 deletions(-)

diff --git a/notmuch-reply.c b/notmuch-reply.c
index 53aefa7..69fd256 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -697,6 +697,7 @@ notmuch_reply_format_headers_only(void *ctx,
 enum {
     FORMAT_DEFAULT,
     FORMAT_JSON,
+    FORMAT_SEXP,
     FORMAT_HEADERS_ONLY,
 };
 
@@ -724,6 +725,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
 	  (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
 				  { "json", FORMAT_JSON },
+				  { "sexp", FORMAT_SEXP },
 				  { "headers-only", FORMAT_HEADERS_ONLY },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
@@ -745,6 +747,9 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     } else if (format == FORMAT_JSON) {
 	reply_format_func = notmuch_reply_format_sprinter;
 	sp = sprinter_json_create (ctx, stdout);
+    } else if (format == FORMAT_SEXP) {
+	reply_format_func = notmuch_reply_format_sprinter;
+	sp = sprinter_sexp_create (ctx, stdout);
     } else {
 	reply_format_func = notmuch_reply_format_default;
     }
diff --git a/notmuch-search.c b/notmuch-search.c
index 830c4e4..6218622 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -305,7 +305,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[] = {
@@ -315,6 +315,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "text", NOTMUCH_FORMAT_TEXT },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
@@ -347,6 +348,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;
     default:
 	/* this should never happen */
 	INTERNAL_ERROR("no output format selected");
diff --git a/notmuch-show.c b/notmuch-show.c
index 38c621f..d4860f1 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -40,6 +40,11 @@ static const notmuch_show_format_t format_json = {
     .part = format_part_sprinter_entry,
 };
 
+static const notmuch_show_format_t format_sexp = {
+    .new_sprinter = sprinter_sexp_create,
+    .part = format_part_sprinter_entry,
+};
+
 static notmuch_status_t
 format_part_mbox (const void *ctx, sprinter_t *sp, mime_node_t *node,
 		  int indent, const notmuch_show_params_t *params);
@@ -110,7 +115,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
 static void
 format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
 {
-    /* Any changes to the JSON format should be reflected in the file
+    /* Any changes to the JSON or S-Expression format should be reflected in the file
      * devel/schemata. */
 
     void *local = talloc_new (NULL);
@@ -1012,6 +1017,7 @@ do_show (void *ctx,
 enum {
     NOTMUCH_FORMAT_NOT_SPECIFIED,
     NOTMUCH_FORMAT_JSON,
+    NOTMUCH_FORMAT_SEXP,
     NOTMUCH_FORMAT_TEXT,
     NOTMUCH_FORMAT_MBOX,
     NOTMUCH_FORMAT_RAW
@@ -1056,6 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
 				  { "text", NOTMUCH_FORMAT_TEXT },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "mbox", NOTMUCH_FORMAT_MBOX },
 				  { "raw", NOTMUCH_FORMAT_RAW },
 				  { 0, 0 } } },
@@ -1100,6 +1107,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
     case NOTMUCH_FORMAT_TEXT:
 	format = &format_text;
 	break;
+    case NOTMUCH_FORMAT_SEXP:
+	format = &format_sexp;
+	break;
     case NOTMUCH_FORMAT_MBOX:
 	if (params.part > 0) {
 	    fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
@@ -1120,7 +1130,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 
     /* Default is entire-thread = FALSE except for format=json. */
     if (entire_thread == ENTIRE_THREAD_DEFAULT) {
-	if (format == &format_json)
+	if (format == &format_json || format == &format_sexp)
 	    entire_thread = ENTIRE_THREAD_TRUE;
 	else
 	    entire_thread = ENTIRE_THREAD_FALSE;
@@ -1131,8 +1141,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	    fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
 	    params.output_body = TRUE;
 	} else {
-	    if (format != &format_json)
-		fprintf (stderr, "Warning: --body=false only implemented for format=json\n");
+	    if (format != &format_json && format != &format_sexp)
+		fprintf (stderr, "Warning: --body=false only implemented for format=json and format=sexp\n");
 	}
     }
 
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v2 4/5] Adding tests for --format=sexp.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (10 preceding siblings ...)
  2012-12-04 14:46 ` [PATCH v2 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
@ 2012-12-04 14:46 ` Peter Feigl
  2012-12-04 19:24   ` Austin Clements
  2012-12-04 14:46 ` [PATCH v2 5/5] Updating man pages for new S-Expression output format Peter Feigl
  12 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-12-04 14:46 UTC (permalink / raw)
  To: notmuch

Add basic tests, the same as for json, for the S-Expression output
format.
---
 test/notmuch-test |  1 +
 test/sexp         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)
 create mode 100755 test/sexp

diff --git a/test/notmuch-test b/test/notmuch-test
index a6ef34f..ca9c3dc 100755
--- a/test/notmuch-test
+++ b/test/notmuch-test
@@ -31,6 +31,7 @@ TESTS="
   excludes
   tagging
   json
+  sexp
   text
   multipart
   thread-naming
diff --git a/test/sexp b/test/sexp
new file mode 100755
index 0000000..fdc9de6
--- /dev/null
+++ b/test/sexp
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+test_description="--format=sexp output"
+. ./test-lib.sh
+
+test_begin_subtest "Show message: sexp"
+add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
+output=$(notmuch show --format=sexp "sexp-show-message")
+test_expect_equal "$output" "(((((id \"msg-001@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-subject\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Bcc \"test_suite+bcc@notmuchmail.org\") (Reply-To \"test_suite+replyto@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"text/plain\") (content \"sexp-show-message\n\"))))) ())))"
+
+# This should be the same output as above.
+test_begin_subtest "Show message: sexp --body=true"
+output=$(notmuch show --format=sexp --body=true "sexp-show-message")
+test_expect_equal "$output" "(((((id \"msg-001@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-subject\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Bcc \"test_suite+bcc@notmuchmail.org\") (Reply-To \"test_suite+replyto@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"text/plain\") (content \"sexp-show-message\n\"))))) ())))"
+
+test_begin_subtest "Show message: sexp --body=false"
+output=$(notmuch show --format=sexp --body=false "sexp-show-message")
+test_expect_equal "$output" "(((((id \"msg-001@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-subject\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Bcc \"test_suite+bcc@notmuchmail.org\") (Reply-To \"test_suite+replyto@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\")))) ())))"
+
+test_begin_subtest "Search message: sexp"
+add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
+output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
+test_expect_equal "$output" "(((thread \"0000000000000002\") (timestamp 946728000) (date_relative \"2000-01-01\") (matched 1) (total 1) (authors \"Notmuch Test Suite\") (subject \"sexp-search-subject\") (tags (\"inbox\" \"unread\"))))"
+
+test_begin_subtest "Show message: sexp, utf-8"
+add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
+output=$(notmuch show --format=sexp "jsön-show-méssage")
+test_expect_equal "$output" "(((((id \"msg-003@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-003\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-utf8-body-sübjéct\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"text/plain\") (content \"jsön-show-méssage\n\"))))) ())))"
+
+test_begin_subtest "Show message: sexp, inline attachment filename"
+subject='sexp-show-inline-attachment-filename'
+id="sexp-show-inline-attachment-filename@notmuchmail.org"
+emacs_deliver_message \
+    "$subject" \
+    'This is a test message with inline attachment with a filename' \
+    "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
+     (message-goto-eoh)
+     (insert \"Message-ID: <$id>\n\")"
+output=$(notmuch show --format=sexp "id:$id")
+filename=$(notmuch search --output=files "id:$id")
+test_expect_equal "$output" "(((((id \"sexp-show-inline-attachment-filename@notmuchmail.org\") (match t) (excluded nil) (filename \"$filename\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\")) (headers ((Subject \"sexp-show-inline-attachment-filename\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"test_suite@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"multipart/mixed\") (content (((id 2) (content-type \"text/plain\") (content \"This is a test message with inline attachment with a filename\")) ((id 3) (content-type \"application/octet-stream\") (filename \"README\")))))))) ())))"
+
+test_begin_subtest "Search message: sexp, utf-8"
+add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
+output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
+test_expect_equal "$output" "(((thread \"0000000000000005\") (timestamp 946728000) (date_relative \"2000-01-01\") (matched 1) (total 1) (authors \"Notmuch Test Suite\") (subject \"sexp-search-utf8-body-sübjéct\") (tags (\"inbox\" \"unread\"))))"
+
+
+test_done
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v2 5/5] Updating man pages for new S-Expression output format.
  2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
                   ` (11 preceding siblings ...)
  2012-12-04 14:46 ` [PATCH v2 4/5] Adding tests for --format=sexp Peter Feigl
@ 2012-12-04 14:46 ` Peter Feigl
  12 siblings, 0 replies; 38+ messages in thread
From: Peter Feigl @ 2012-12-04 14:46 UTC (permalink / raw)
  To: notmuch

Add sections about the new S-Expression output format (--format=sexp) to
the notmuch-search, notmuch-reply and notmuch-show man pages.
---
 man/man1/notmuch-reply.1  | 14 ++++++++++----
 man/man1/notmuch-search.1 | 15 ++++++++-------
 man/man1/notmuch-show.1   | 36 ++++++++++++++++++++++++++++--------
 3 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1
index d264060..8ee1805 100644
--- a/man/man1/notmuch-reply.1
+++ b/man/man1/notmuch-reply.1
@@ -37,7 +37,7 @@ Supported options for
 include
 .RS
 .TP 4
-.BR \-\-format= ( default | json | headers\-only )
+.BR \-\-format= ( default | json | sexp | headers\-only )
 .RS
 .TP 4
 .BR default
@@ -48,6 +48,11 @@ Produces JSON output containing headers for a reply message and the
 contents of the original message. This output can be used by a client
 to create a reply message intelligently.
 .TP
+.BR sexp
+Produces S-Expression output containing headers for a reply message and 
+the contents of the original message. This output can be used by a client
+to create a reply message intelligently.
+.TP
 .BR headers\-only
 Only produces In\-Reply\-To, References, To, Cc, and Bcc headers.
 .RE
@@ -74,8 +79,8 @@ user's addresses.
 
 Decrypt any MIME encrypted parts found in the selected content
 (ie. "multipart/encrypted" parts). Status of the decryption will be
-reported (currently only supported with --format=json) and the
-multipart/encrypted part will be replaced by the decrypted
+reported (currently only supported with --format=json and --format=sexp)
+and the multipart/encrypted part will be replaced by the decrypted
 content.
 .RE
 
@@ -89,7 +94,8 @@ id:<message-id>), but it can be useful to reply to several messages at
 once. For example, when a series of patches are sent in a single
 thread, replying to the entire thread allows for the reply to comment
 on issues found in multiple patches. The default format supports
-replying to multiple messages at once, but the JSON format does not.
+replying to multiple messages at once, but the JSON and S-Expression 
+format does not.
 .RE
 .RE
 
diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1
index 6ccd3b8..498232d 100644
--- a/man/man1/notmuch-search.1
+++ b/man/man1/notmuch-search.1
@@ -25,9 +25,9 @@ Supported options for
 include
 .RS 4
 .TP 4
-.BR \-\-format= ( json | text )
+.BR \-\-format= ( json | sexp | text )
 
-Presents the results in either JSON or plain-text (default).
+Presents the results in either JSON, S-Expressions or plain-text (default).
 .RE
 
 .RS 4
@@ -49,7 +49,7 @@ the authors of the thread and the subject.
 
 Output the thread IDs of all threads with any message matching the
 search terms, either one per line (\-\-format=text) or as a JSON array
-(\-\-format=json).
+(\-\-format=json) or an S-Expression list (\-\-format=sexp).
 .RE
 .RS 4
 .TP 4
@@ -57,22 +57,23 @@ search terms, either one per line (\-\-format=text) or as a JSON array
 
 Output the message IDs of all messages matching the search terms,
 either one per line (\-\-format=text) or as a JSON array
-(\-\-format=json).
+(\-\-format=json) or as an S-Expression list (\-\-format=sexp).
 .RE
 .RS 4
 .TP 4
 .B files
 
 Output the filenames of all messages matching the search terms, either
-one per line (\-\-format=text) or as a JSON array (\-\-format=json).
+one per line (\-\-format=text) or as a JSON array (\-\-format=json) or 
+as an S-Expression list (\-\-format=sexp).
 .RE
 .RS 4
 .TP 4
 .B tags
 
 Output all tags that appear on any message matching the search terms,
-either one per line (\-\-format=text) or as a JSON array
-(\-\-format=json).
+either one per line (\-\-format=text) or as a JSON array (\-\-format=json) 
+or as an S-Expression list (\-\-format=sexp).
 .RE
 .RE
 
diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1
index 4481f21..25b5bb8 100644
--- a/man/man1/notmuch-show.1
+++ b/man/man1/notmuch-show.1
@@ -31,12 +31,14 @@ If true,
 outputs all messages in the thread of any message matching the search
 terms; if false, it outputs only the matching messages. For
 .B --format=json
+and
+.B --format=sexp
 this defaults to true.  For other formats, this defaults to false.
 .RE
 
 .RS 4
 .TP 4
-.B \-\-format=(text|json|mbox|raw)
+.B \-\-format=(text|json|sexp|mbox|raw)
 
 .RS 4
 .TP 4
@@ -60,11 +62,29 @@ format is more robust than the text format for automated
 processing. The nested structure of multipart MIME messages is
 reflected in nested JSON output. By default JSON output includes all
 messages in a matching thread; that is, by default,
+
 .B \-\-format=json
 sets
 .B "\-\-entire\-thread"
 The caller can disable this behaviour by setting
 .B \-\-entire\-thread=false
+.RE
+.RS 4
+.TP 4
+.B sexp
+
+The output is formatted as an S-Expression (sexp). This
+format is more robust than the text format for automated
+processing. The nested structure of multipart MIME messages is
+reflected in nested S-Expression output. By default, 
+S-Expression output includes all messages in a matching thread; 
+that is, by default,
+
+.B \-\-format=sexp
+sets
+.B "\-\-entire\-thread"
+The caller can disable this behaviour by setting
+.B \-\-entire\-thread=false
 
 .RE
 .RS 4
@@ -113,7 +133,7 @@ message.
 Output the single decoded MIME part N of a single message.  The search
 terms must match only a single message.  Message parts are numbered in
 a depth-first walk of the message MIME structure, and are identified
-in the 'json' or 'text' output formats.
+in the 'json', 'sexp' or 'text' output formats.
 .RE
 
 .RS 4
@@ -123,8 +143,8 @@ in the 'json' or 'text' output formats.
 Compute and report the validity of any MIME cryptographic signatures
 found in the selected content (ie. "multipart/signed" parts). Status
 of the signature will be reported (currently only supported with
---format=json), and the multipart/signed part will be replaced by the
-signed data.
+--format=json and --format=sexp), and the multipart/signed part 
+will be replaced by the signed data.
 .RE
 
 .RS 4
@@ -133,9 +153,9 @@ signed data.
 
 Decrypt any MIME encrypted parts found in the selected content
 (ie. "multipart/encrypted" parts). Status of the decryption will be
-reported (currently only supported with --format=json) and the
-multipart/encrypted part will be replaced by the decrypted
-content.  Implies --verify.
+reported (currently only supported with --format=json and 
+--format=sexp) and the multipart/encrypted part will be replaced
+by the decrypted content.  Implies --verify.
 .RE
 
 .RS 4
@@ -166,7 +186,7 @@ If true (the default)
 includes the bodies of the messages in the output; if false,
 bodies are omitted.
 .B --body=false
-is only implemented for the json format and it is incompatible with
+is only implemented for the json and sexp formats and it is incompatible with
 .B --part > 0.
 
 This is useful if the caller only needs the headers as body-less
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH v2 0/5] New output format sexp (Lisp S-Expressions)
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
@ 2012-12-04 17:07   ` Jani Nikula
  2012-12-06  7:33   ` [PATCH v3 " Peter Feigl
                     ` (5 subsequent siblings)
  6 siblings, 0 replies; 38+ messages in thread
From: Jani Nikula @ 2012-12-04 17:07 UTC (permalink / raw)
  To: Peter Feigl, notmuch


Hi Peter -

On Tue, 04 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> This patch series adds a new output format "sexp" to notmuch-reply,
> notmuch-show and notmuch-search. These are useful for the Android mobile
> client and perhaps other Lisp programs as well.
> After the switch to a generic structured output printer, which was
> committed some months ago, these patches just add another one (like the
> json structured output printer).
> Basic tests and updates to the man pages are also included.
>
>
> Peter Feigl (5):
>   Adding an S-expression structured output printer.
>   Rename the -json printer functions in notmuch-reply and    
>     notmuch-show to generic -sprinter functions.
>   Use the S-Expression structured printer in notmuch-show,
>     notmuch-reply     and notmuch-search.

Patches 1-3 look good.

>   Adding tests for --format=sexp.

Did not review.

>   Updating man pages for new S-Expression output format.

Did not review, but noticed it adds lines with trailing whitespace.


BR,
Jani.


>
>  Makefile.local            |   1 +
>  man/man1/notmuch-reply.1  |  14 ++-
>  man/man1/notmuch-search.1 |  15 +--
>  man/man1/notmuch-show.1   |  36 +++++--
>  notmuch-client.h          |   8 +-
>  notmuch-reply.c           |  43 ++++----
>  notmuch-search.c          |   6 +-
>  notmuch-show.c            |  48 +++++----
>  sprinter-sexp.c           | 250 ++++++++++++++++++++++++++++++++++++++++++++++
>  sprinter.h                |   4 +
>  test/notmuch-test         |   1 +
>  test/sexp                 |  48 +++++++++
>  12 files changed, 414 insertions(+), 60 deletions(-)
>  create mode 100644 sprinter-sexp.c
>  create mode 100755 test/sexp
>
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v2 1/5] Adding an S-expression structured output printer.
  2012-12-04 14:46 ` [PATCH v2 1/5] Adding an S-expression structured output printer Peter Feigl
@ 2012-12-04 19:14   ` Austin Clements
  0 siblings, 0 replies; 38+ messages in thread
From: Austin Clements @ 2012-12-04 19:14 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Tue, 04 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> This commit adds a structured output printer for Lisp
> S-Expressions. Later commits will use this printer in notmuch search,
> show and reply.
>
> The structure is the same as json, but:
> - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
> - maps are written as a-lists: ((key "value") (other-key "other-value"))

I thought the plan was to use plists.  Or are we going to support both?

> - true is written as t
> - false is written as nil
> - null is written as nil
> ---
>  Makefile.local  |   1 +
>  sprinter-sexp.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  sprinter.h      |   4 +
>  3 files changed, 255 insertions(+)
>  create mode 100644 sprinter-sexp.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 2b91946..0db1713 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -270,6 +270,7 @@ notmuch_client_srcs =		\
>  	notmuch-tag.c		\
>  	notmuch-time.c		\
>  	sprinter-json.c		\
> +	sprinter-sexp.c		\
>  	sprinter-text.c		\
>  	query-string.c		\
>  	mime-node.c		\
> diff --git a/sprinter-sexp.c b/sprinter-sexp.c
> new file mode 100644
> index 0000000..6d6bbad
> --- /dev/null
> +++ b/sprinter-sexp.c
> @@ -0,0 +1,250 @@
> +/* notmuch - Not much of an email program, (just index and search)
> + *
> + * Copyright © 2012 Carl Worth

This should probably be your name.

> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see http://www.gnu.org/licenses/ .
> + *
> + * Author: Carl Worth <cworth@cworth.org>

Same here.

> + */
> +
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <talloc.h>
> +#include "sprinter.h"
> +
> +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;
> +
> +    /* A flag to signify that a separator should be inserted in the
> +     * output as soon as possible.
> +     */
> +    notmuch_bool_t insert_separator;
> +};
> +
> +struct sexp_state {
> +    struct sexp_state *parent;
> +
> +    /* True if nothing has been printed in this aggregate yet.
> +     * Suppresses the space before a value. */
> +    notmuch_bool_t first;
> +
> +    /* True if the state is a map state.
> +     * Used to add a space between key/value pairs. */
> +    notmuch_bool_t in_map;

Maybe in_alist?

> +
> +    /* The character that closes the current aggregate. */
> +    char close;

Given that the close character is always ')', why have this field?

> +};
> +
> +/* Helper function to set up the stream to print a value.  If this
> + * value follows another value, prints a space. */
> +static struct sprinter_sexp *
> +sexp_begin_value (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if (sps->state) {
> +	if (! sps->state->first) {
> +	    if (sps->insert_separator) {
> +		fputc ('\n', sps->stream);
> +		sps->insert_separator = FALSE;
> +	    } else {
> +		if (! sps->state->in_map)
> +		    fputc (' ', sps->stream);
> +	    }
> +	} else {
> +	    sps->state->first = FALSE;
> +	}
> +    }
> +    return sps;
> +}
> +
> +/* Helper function to begin an aggregate type.  Prints the open
> + * character and pushes a new state frame. */
> +static void
> +sexp_begin_aggregate (struct sprinter *sp, char open, char close)

The open and close arguments seem unnecessary here, since they're always
'(' and ')'.  Perhaps this should instead take in_map as an argument?

> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +    struct sexp_state *state = talloc (sps, struct sexp_state);
> +    fputc (open, sps->stream);
> +    state->parent = sps->state;
> +    state->first = TRUE;
> +    state->in_map = FALSE;
> +    state->close = close;
> +    sps->state = state;
> +}
> +
> +static void
> +sexp_begin_map (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    sexp_begin_aggregate (sp, '(', ')');
> +    sps->state->in_map = TRUE;
> +}
> +
> +static void
> +sexp_begin_list (struct sprinter *sp)
> +{
> +    sexp_begin_aggregate (sp, '(', ')');
> +}
> +
> +static void
> +sexp_end (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +    struct sexp_state *state = sps->state;
> +
> +    if (sps->state->in_map)
> +	fputc (')', sps->stream);
> +    fputc (sps->state->close, sps->stream);
> +    sps->state = state->parent;
> +    talloc_free (state);
> +    if (sps->state == NULL)
> +	fputc ('\n', sps->stream);
> +}
> +
> +static void
> +sexp_string_len_internal (struct sprinter *sp, const char *val, size_t len, notmuch_bool_t quote)
> +{
> +    static const char *const escapes[] = {
> +	['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
> +	['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"

It's unfortunate that different Lisps have different string escaping
conventions.  All of these will work in Elisp.  R5RS only specifies \"
and \\ (anything else is unspecified, though at least MIT Scheme, Racket
R5RS, and Chicken support the others).  R6RS specifies all of these.  In
Common Lisp, \" and \\ work as expected, but \ before anything else will
just ignore the \ (so "\n" is the same as "n").

Conveniently, in all of these, no characters other than " and \ actually
need escaping, so I'd be inclined to print any other character
literally.

Consumers will have to be sure to use a UTF-8 encoding when reading.

> +    };
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    if(quote)
> +	fputc ('"', sps->stream);
> +    for (; len; ++val, --len) {
> +	unsigned char ch = *val;
> +	if (ch < ARRAY_SIZE (escapes) && escapes[ch])
> +	    fputs (escapes[ch], sps->stream);
> +	else if (ch >= 32)
> +	    fputc (ch, sps->stream);
> +	else
> +	    fprintf (sps->stream, "\\u%04x", ch);

If we do have to include numeric character escapes, "\\%03o" would be
better.  As mentioned above, R5RS and Common Lisp have no means to do
this.  Even worse, R6RS doesn't specify octal escapes but does specify
"\xNN;" (note the semicolon), which isn't compatible with *anything*,
including most R5RS implementations.  In practice, though, most things
seem to accept the octal escape I suggested (confirmed in Elisp, MIT
Scheme, Racket R5RS, and Chicken).

> +    }
> +    if(quote)
> +	fputc ('"', sps->stream);
> +}
> +
> +static void
> +sexp_string_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, TRUE); /* print quoted */
> +}
> +
> +static void
> +sexp_symbol_len (struct sprinter *sp, const char *val, size_t len)
> +{
> +    sexp_string_len_internal (sp, val, len, FALSE); /* print unquoted */

I don't understand this.  The quoting rules for symbols are completely
different from the rules for strings.  It seems like it would be better
to print the symbol literally with fputs than to apply incorrect quoting
rules to it.  Even better would be to INTERNAL_ERROR if the symbol
contains any characters that might require escaping, though if someone
does introduce such a symbol, they'll probably find out quickly enough.

> +}
> +
> +static void
> +sexp_string (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +	val = "";
> +    sexp_string_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_symbol (struct sprinter *sp, const char *val)
> +{
> +    if (val == NULL)
> +	val = "";
> +    sexp_symbol_len (sp, val, strlen (val));
> +}
> +
> +static void
> +sexp_integer (struct sprinter *sp, int val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fprintf (sps->stream, "%d", val);
> +}
> +
> +static void
> +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs (val ? "t" : "nil", sps->stream);
> +}
> +
> +static void
> +sexp_null (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = sexp_begin_value (sp);
> +
> +    fputs ("nil", sps->stream);
> +}
> +
> +static void
> +sexp_map_key (struct sprinter *sp, const char *key)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    if (sps->state->in_map && ! sps->state->first)
> +	fputs (") ", sps->stream);
> +    fputc ('(', sps->stream);
> +    sexp_symbol (sp, key);

Since this is the only use of sexp_symbol, perhaps the code should be
folded in?  At least sexp_symbol and sexp_symbol_len should be combined
into sexp_symbol.

> +    fputc (' ', sps->stream);
> +}
> +
> +static void
> +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
> +{
> +}
> +
> +static void
> +sexp_separator (struct sprinter *sp)
> +{
> +    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
> +
> +    sps->insert_separator = TRUE;
> +}
> +
> +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,
> +	    .string_len = sexp_string_len,
> +	    .integer = sexp_integer,
> +	    .boolean = sexp_boolean,
> +	    .null = sexp_null,
> +	    .map_key = sexp_map_key,
> +	    .separator = sexp_separator,
> +	    .set_prefix = sexp_set_prefix,
> +	    .is_text_printer = FALSE,
> +	}
> +    };
> +    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 912a526..59776a9 100644
> --- a/sprinter.h
> +++ b/sprinter.h
> @@ -70,4 +70,8 @@ sprinter_text_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.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v2 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions.
  2012-12-04 14:46 ` [PATCH v2 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
@ 2012-12-04 19:18   ` Austin Clements
  0 siblings, 0 replies; 38+ messages in thread
From: Austin Clements @ 2012-12-04 19:18 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Tue, 04 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> All the structured output functions in notmuch-reply and notmuch-show
> are renamed to a generic name (as they do not contain any json-specific
> code anyway). This patch is a preparation to actually using the new
> S-Expression sprinter in notmuch-reply and notmuch-show.
> ---
>  notmuch-client.h |  8 ++++----
>  notmuch-reply.c  | 38 +++++++++++++++++++++-----------------
>  notmuch-show.c   | 30 +++++++++++++++---------------
>  3 files changed, 40 insertions(+), 36 deletions(-)
>
> diff --git a/notmuch-client.h b/notmuch-client.h
> index ae9344b..1c336dc 100644
> --- a/notmuch-client.h
> +++ b/notmuch-client.h
> @@ -175,12 +175,12 @@ notmuch_status_t
>  show_one_part (const char *filename, int part);
>  
>  void
> -format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node,
> -		  notmuch_bool_t first, notmuch_bool_t output_body);
> +format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
> +		      notmuch_bool_t first, notmuch_bool_t output_body);
>  
>  void
> -format_headers_json (struct sprinter *sp, GMimeMessage *message,
> -		     notmuch_bool_t reply);
> +format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
> +			 notmuch_bool_t reply);
>  
>  typedef enum {
>      NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
> diff --git a/notmuch-reply.c b/notmuch-reply.c
> index e60a264..53aefa7 100644
> --- a/notmuch-reply.c
> +++ b/notmuch-reply.c
> @@ -548,7 +548,8 @@ notmuch_reply_format_default(void *ctx,
>  			     notmuch_config_t *config,
>  			     notmuch_query_t *query,
>  			     notmuch_show_params_t *params,
> -			     notmuch_bool_t reply_all)
> +			     notmuch_bool_t reply_all,
> +			     unused (sprinter_t *sp))
>  {
>      GMimeMessage *reply;
>      notmuch_messages_t *messages;
> @@ -587,17 +588,17 @@ notmuch_reply_format_default(void *ctx,
>  }
>  
>  static int
> -notmuch_reply_format_json(void *ctx,
> -			  notmuch_config_t *config,
> -			  notmuch_query_t *query,
> -			  notmuch_show_params_t *params,
> -			  notmuch_bool_t reply_all)
> +notmuch_reply_format_sprinter(void *ctx,
> +			      notmuch_config_t *config,
> +			      notmuch_query_t *query,
> +			      notmuch_show_params_t *params,
> +			      notmuch_bool_t reply_all,
> +			      sprinter_t *sp)
>  {
>      GMimeMessage *reply;
>      notmuch_messages_t *messages;
>      notmuch_message_t *message;
>      mime_node_t *node;
> -    sprinter_t *sp;
>  
>      if (notmuch_query_count_messages (query) != 1) {
>  	fprintf (stderr, "Error: search term did not match precisely one message.\n");
> @@ -613,18 +614,17 @@ notmuch_reply_format_json(void *ctx,
>      if (!reply)
>  	return 1;
>  
> -    sp = sprinter_json_create (ctx, stdout);
>      sp->begin_map (sp);
>  
>      /* The headers of the reply message we've created */
>      sp->map_key (sp, "reply-headers");
> -    format_headers_json (sp, reply, TRUE);
> +    format_headers_sprinter (sp, reply, TRUE);
>      g_object_unref (G_OBJECT (reply));
>      reply = NULL;
>  
>      /* Start the original */
>      sp->map_key (sp, "original");
> -    format_part_json (ctx, sp, node, TRUE, TRUE);
> +    format_part_sprinter (ctx, sp, node, TRUE, TRUE);
>  
>      /* End */
>      sp->end (sp);
> @@ -639,7 +639,8 @@ notmuch_reply_format_headers_only(void *ctx,
>  				  notmuch_config_t *config,
>  				  notmuch_query_t *query,
>  				  unused (notmuch_show_params_t *params),
> -				  notmuch_bool_t reply_all)
> +				  notmuch_bool_t reply_all,
> +				  unused (sprinter_t *sp))
>  {
>      GMimeMessage *reply;
>      notmuch_messages_t *messages;
> @@ -707,7 +708,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>      notmuch_query_t *query;
>      char *query_string;
>      int opt_index, ret = 0;
> -    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
> +    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all, struct sprinter *sp);

Yikes.  This could use some line wrapping.

This patch LGTM other than this.

>      notmuch_show_params_t params = {
>  	.part = -1,
>  	.crypto = {
> @@ -717,6 +718,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>      };
>      int format = FORMAT_DEFAULT;
>      int reply_all = TRUE;
> +    struct sprinter *sp = NULL;
>  
>      notmuch_opt_desc_t options[] = {
>  	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
> @@ -738,12 +740,14 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>  	return 1;
>      }
>  
> -    if (format == FORMAT_HEADERS_ONLY)
> +    if (format == FORMAT_HEADERS_ONLY) {
>  	reply_format_func = notmuch_reply_format_headers_only;
> -    else if (format == FORMAT_JSON)
> -	reply_format_func = notmuch_reply_format_json;
> -    else
> +    } else if (format == FORMAT_JSON) {
> +	reply_format_func = notmuch_reply_format_sprinter;
> +	sp = sprinter_json_create (ctx, stdout);
> +    } else {
>  	reply_format_func = notmuch_reply_format_default;
> +    }
>  
>      config = notmuch_config_open (ctx, NULL, NULL);
>      if (config == NULL)
> @@ -770,7 +774,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>  	return 1;
>      }
>  
> -    if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
> +    if (reply_format_func (ctx, config, query, &params, reply_all, sp) != 0)
>  	return 1;
>  
>      notmuch_crypto_cleanup (&params.crypto);
> diff --git a/notmuch-show.c b/notmuch-show.c
> index 2fa2292..38c621f 100644
> --- a/notmuch-show.c
> +++ b/notmuch-show.c
> @@ -32,12 +32,12 @@ static const notmuch_show_format_t format_text = {
>  };
>  
>  static notmuch_status_t
> -format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
> +format_part_sprinter_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  			int indent, const notmuch_show_params_t *params);
>  
>  static const notmuch_show_format_t format_json = {
>      .new_sprinter = sprinter_json_create,
> -    .part = format_part_json_entry,
> +    .part = format_part_sprinter_entry,
>  };
>  
>  static notmuch_status_t
> @@ -108,7 +108,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
>  /* Emit a sequence of key/value pairs for the metadata of message.
>   * The caller should begin a map before calling this. */
>  static void
> -format_message_json (sprinter_t *sp, notmuch_message_t *message)
> +format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
>  {
>      /* Any changes to the JSON format should be reflected in the file
>       * devel/schemata. */
> @@ -208,7 +208,7 @@ _is_from_line (const char *line)
>  }
>  
>  void
> -format_headers_json (sprinter_t *sp, GMimeMessage *message,
> +format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
>  		     notmuch_bool_t reply)
>  {
>      /* Any changes to the JSON format should be reflected in the file
> @@ -363,7 +363,7 @@ signer_status_to_string (GMimeSignerStatus x)
>  
>  #ifdef GMIME_ATLEAST_26
>  static void
> -format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
> +format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
>  {
>      /* Any changes to the JSON format should be reflected in the file
>       * devel/schemata. */
> @@ -438,7 +438,7 @@ format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
>  }
>  #else
>  static void
> -format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
> +format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
>  {
>      const GMimeSignatureValidity* validity = node->sig_validity;
>  
> @@ -595,7 +595,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  }
>  
>  void
> -format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
> +format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  		  notmuch_bool_t first, notmuch_bool_t output_body)
>  {
>      /* Any changes to the JSON format should be reflected in the file
> @@ -603,15 +603,15 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  
>      if (node->envelope_file) {
>  	sp->begin_map (sp);
> -	format_message_json (sp, node->envelope_file);
> +	format_message_sprinter (sp, node->envelope_file);
>  
>  	sp->map_key (sp, "headers");
> -	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
> +	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
>  
>  	if (output_body) {
>  	    sp->map_key (sp, "body");
>  	    sp->begin_list (sp);
> -	    format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE);
> +	    format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE);
>  	    sp->end (sp);
>  	}
>  	sp->end (sp);
> @@ -646,7 +646,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  
>      if (node->verify_attempted) {
>  	sp->map_key (sp, "sigstatus");
> -	format_part_sigstatus_json (sp, node);
> +	format_part_sigstatus_sprinter (sp, node);
>      }
>  
>      sp->map_key (sp, "content-type");
> @@ -698,7 +698,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  	sp->begin_map (sp);
>  
>  	sp->map_key (sp, "headers");
> -	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
> +	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
>  
>  	sp->map_key (sp, "body");
>  	sp->begin_list (sp);
> @@ -706,7 +706,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>      }
>  
>      for (i = 0; i < node->nchildren; i++)
> -	format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
> +	format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
>  
>      /* Close content structures */
>      for (i = 0; i < nclose; i++)
> @@ -716,11 +716,11 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  }
>  
>  static notmuch_status_t
> -format_part_json_entry (const void *ctx, sprinter_t *sp,
> +format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
>  			mime_node_t *node, unused (int indent),
>  			const notmuch_show_params_t *params)
>  {
> -    format_part_json (ctx, sp, node, TRUE, params->output_body);
> +    format_part_sprinter (ctx, sp, node, TRUE, params->output_body);
>  
>      return NOTMUCH_STATUS_SUCCESS;
>  }
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v2 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-04 14:46 ` [PATCH v2 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
@ 2012-12-04 19:22   ` Austin Clements
  0 siblings, 0 replies; 38+ messages in thread
From: Austin Clements @ 2012-12-04 19:22 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Tue, 04 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> This patch uses the new S-Expression printer in the notmuch CLI (show,
> search and reply). You can now use --format=sexp for any of them.
> ---
>  notmuch-reply.c  |  5 +++++
>  notmuch-search.c |  6 +++++-
>  notmuch-show.c   | 18 ++++++++++++++----
>  3 files changed, 24 insertions(+), 5 deletions(-)
>
> diff --git a/notmuch-reply.c b/notmuch-reply.c
> index 53aefa7..69fd256 100644
> --- a/notmuch-reply.c
> +++ b/notmuch-reply.c
> @@ -697,6 +697,7 @@ notmuch_reply_format_headers_only(void *ctx,
>  enum {
>      FORMAT_DEFAULT,
>      FORMAT_JSON,
> +    FORMAT_SEXP,
>      FORMAT_HEADERS_ONLY,
>  };
>  
> @@ -724,6 +725,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>  	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
>  	  (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
>  				  { "json", FORMAT_JSON },
> +				  { "sexp", FORMAT_SEXP },

"sexpa" if we're going to support both alists and plists?  Same for the
others.

>  				  { "headers-only", FORMAT_HEADERS_ONLY },
>  				  { 0, 0 } } },
>  	{ NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
> @@ -745,6 +747,9 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
>      } else if (format == FORMAT_JSON) {
>  	reply_format_func = notmuch_reply_format_sprinter;
>  	sp = sprinter_json_create (ctx, stdout);
> +    } else if (format == FORMAT_SEXP) {
> +	reply_format_func = notmuch_reply_format_sprinter;
> +	sp = sprinter_sexp_create (ctx, stdout);
>      } else {
>  	reply_format_func = notmuch_reply_format_default;
>      }
> diff --git a/notmuch-search.c b/notmuch-search.c
> index 830c4e4..6218622 100644
> --- a/notmuch-search.c
> +++ b/notmuch-search.c
> @@ -305,7 +305,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[] = {
> @@ -315,6 +315,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
>  				  { 0, 0 } } },
>  	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
>  	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
> +				  { "sexp", NOTMUCH_FORMAT_SEXP },
>  				  { "text", NOTMUCH_FORMAT_TEXT },
>  				  { 0, 0 } } },
>  	{ NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
> @@ -347,6 +348,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;
>      default:
>  	/* this should never happen */
>  	INTERNAL_ERROR("no output format selected");
> diff --git a/notmuch-show.c b/notmuch-show.c
> index 38c621f..d4860f1 100644
> --- a/notmuch-show.c
> +++ b/notmuch-show.c
> @@ -40,6 +40,11 @@ static const notmuch_show_format_t format_json = {
>      .part = format_part_sprinter_entry,
>  };
>  
> +static const notmuch_show_format_t format_sexp = {
> +    .new_sprinter = sprinter_sexp_create,
> +    .part = format_part_sprinter_entry,
> +};
> +
>  static notmuch_status_t
>  format_part_mbox (const void *ctx, sprinter_t *sp, mime_node_t *node,
>  		  int indent, const notmuch_show_params_t *params);
> @@ -110,7 +115,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
>  static void
>  format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
>  {
> -    /* Any changes to the JSON format should be reflected in the file
> +    /* Any changes to the JSON or S-Expression format should be reflected in the file
>       * devel/schemata. */

Please re-wrap this comment to 72 columns.

>  
>      void *local = talloc_new (NULL);
> @@ -1012,6 +1017,7 @@ do_show (void *ctx,
>  enum {
>      NOTMUCH_FORMAT_NOT_SPECIFIED,
>      NOTMUCH_FORMAT_JSON,
> +    NOTMUCH_FORMAT_SEXP,
>      NOTMUCH_FORMAT_TEXT,
>      NOTMUCH_FORMAT_MBOX,
>      NOTMUCH_FORMAT_RAW
> @@ -1056,6 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>  	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
>  	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
>  				  { "text", NOTMUCH_FORMAT_TEXT },
> +				  { "sexp", NOTMUCH_FORMAT_SEXP },
>  				  { "mbox", NOTMUCH_FORMAT_MBOX },
>  				  { "raw", NOTMUCH_FORMAT_RAW },
>  				  { 0, 0 } } },
> @@ -1100,6 +1107,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>      case NOTMUCH_FORMAT_TEXT:
>  	format = &format_text;
>  	break;
> +    case NOTMUCH_FORMAT_SEXP:
> +	format = &format_sexp;
> +	break;
>      case NOTMUCH_FORMAT_MBOX:
>  	if (params.part > 0) {
>  	    fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
> @@ -1120,7 +1130,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>  
>      /* Default is entire-thread = FALSE except for format=json. */
>      if (entire_thread == ENTIRE_THREAD_DEFAULT) {
> -	if (format == &format_json)
> +	if (format == &format_json || format == &format_sexp)

Good catch.

>  	    entire_thread = ENTIRE_THREAD_TRUE;
>  	else
>  	    entire_thread = ENTIRE_THREAD_FALSE;
> @@ -1131,8 +1141,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
>  	    fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
>  	    params.output_body = TRUE;
>  	} else {
> -	    if (format != &format_json)
> -		fprintf (stderr, "Warning: --body=false only implemented for format=json\n");
> +	    if (format != &format_json && format != &format_sexp)
> +		fprintf (stderr, "Warning: --body=false only implemented for format=json and format=sexp\n");
>  	}
>      }
>  
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v2 4/5] Adding tests for --format=sexp.
  2012-12-04 14:46 ` [PATCH v2 4/5] Adding tests for --format=sexp Peter Feigl
@ 2012-12-04 19:24   ` Austin Clements
  0 siblings, 0 replies; 38+ messages in thread
From: Austin Clements @ 2012-12-04 19:24 UTC (permalink / raw)
  To: Peter Feigl, notmuch

It would be nice to use something like test_expect_equal_json for this
(probably based on Emacs' pp function), but this is fine for now.

On Tue, 04 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> Add basic tests, the same as for json, for the S-Expression output
> format.
> ---
>  test/notmuch-test |  1 +
>  test/sexp         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 49 insertions(+)
>  create mode 100755 test/sexp
>
> diff --git a/test/notmuch-test b/test/notmuch-test
> index a6ef34f..ca9c3dc 100755
> --- a/test/notmuch-test
> +++ b/test/notmuch-test
> @@ -31,6 +31,7 @@ TESTS="
>    excludes
>    tagging
>    json
> +  sexp
>    text
>    multipart
>    thread-naming
> diff --git a/test/sexp b/test/sexp
> new file mode 100755
> index 0000000..fdc9de6
> --- /dev/null
> +++ b/test/sexp
> @@ -0,0 +1,48 @@
> +#!/usr/bin/env bash
> +test_description="--format=sexp output"
> +. ./test-lib.sh
> +
> +test_begin_subtest "Show message: sexp"
> +add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
> +output=$(notmuch show --format=sexp "sexp-show-message")
> +test_expect_equal "$output" "(((((id \"msg-001@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-subject\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Bcc \"test_suite+bcc@notmuchmail.org\") (Reply-To \"test_suite+replyto@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"text/plain\") (content \"sexp-show-message\n\"))))) ())))"
> +
> +# This should be the same output as above.
> +test_begin_subtest "Show message: sexp --body=true"
> +output=$(notmuch show --format=sexp --body=true "sexp-show-message")
> +test_expect_equal "$output" "(((((id \"msg-001@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-subject\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Bcc \"test_suite+bcc@notmuchmail.org\") (Reply-To \"test_suite+replyto@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"text/plain\") (content \"sexp-show-message\n\"))))) ())))"
> +
> +test_begin_subtest "Show message: sexp --body=false"
> +output=$(notmuch show --format=sexp --body=false "sexp-show-message")
> +test_expect_equal "$output" "(((((id \"msg-001@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-subject\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Bcc \"test_suite+bcc@notmuchmail.org\") (Reply-To \"test_suite+replyto@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\")))) ())))"
> +
> +test_begin_subtest "Search message: sexp"
> +add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
> +output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
> +test_expect_equal "$output" "(((thread \"0000000000000002\") (timestamp 946728000) (date_relative \"2000-01-01\") (matched 1) (total 1) (authors \"Notmuch Test Suite\") (subject \"sexp-search-subject\") (tags (\"inbox\" \"unread\"))))"
> +
> +test_begin_subtest "Show message: sexp, utf-8"
> +add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
> +output=$(notmuch show --format=sexp "jsön-show-méssage")
> +test_expect_equal "$output" "(((((id \"msg-003@notmuch-test-suite\") (match t) (excluded nil) (filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-003\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\" \"unread\")) (headers ((Subject \"sexp-show-utf8-body-sübjéct\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"text/plain\") (content \"jsön-show-méssage\n\"))))) ())))"
> +
> +test_begin_subtest "Show message: sexp, inline attachment filename"
> +subject='sexp-show-inline-attachment-filename'
> +id="sexp-show-inline-attachment-filename@notmuchmail.org"
> +emacs_deliver_message \
> +    "$subject" \
> +    'This is a test message with inline attachment with a filename' \
> +    "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
> +     (message-goto-eoh)
> +     (insert \"Message-ID: <$id>\n\")"
> +output=$(notmuch show --format=sexp "id:$id")
> +filename=$(notmuch search --output=files "id:$id")
> +test_expect_equal "$output" "(((((id \"sexp-show-inline-attachment-filename@notmuchmail.org\") (match t) (excluded nil) (filename \"$filename\") (timestamp 946728000) (date_relative \"2000-01-01\") (tags (\"inbox\")) (headers ((Subject \"sexp-show-inline-attachment-filename\") (From \"Notmuch Test Suite <test_suite@notmuchmail.org>\") (To \"test_suite@notmuchmail.org\") (Date \"Sat, 01 Jan 2000 12:00:00 +0000\"))) (body (((id 1) (content-type \"multipart/mixed\") (content (((id 2) (content-type \"text/plain\") (content \"This is a test message with inline attachment with a filename\")) ((id 3) (content-type \"application/octet-stream\") (filename \"README\")))))))) ())))"
> +
> +test_begin_subtest "Search message: sexp, utf-8"
> +add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
> +output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
> +test_expect_equal "$output" "(((thread \"0000000000000005\") (timestamp 946728000) (date_relative \"2000-01-01\") (matched 1) (total 1) (authors \"Notmuch Test Suite\") (subject \"sexp-search-utf8-body-sübjéct\") (tags (\"inbox\" \"unread\"))))"
> +
> +
> +test_done
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v3 0/5] New output format sexp (Lisp S-Expressions)
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
  2012-12-04 17:07   ` Jani Nikula
@ 2012-12-06  7:33   ` Peter Feigl
  2012-12-06  8:22     ` Mark Walters
  2012-12-07  2:54     ` Jameson Graef Rollins
  2012-12-06  7:33   ` [PATCH v3 1/5] Adding an S-expression structured output printer Peter Feigl
                     ` (4 subsequent siblings)
  6 siblings, 2 replies; 38+ messages in thread
From: Peter Feigl @ 2012-12-06  7:33 UTC (permalink / raw)
  To: notmuch

This patch series adds a new output format "sexp" to notmuch-reply,
notmuch-show and notmuch-search. These are useful for the Android mobile
client and perhaps other Lisp programs as well.
After the switch to a generic structured output printer, which was
committed some months ago, these patches just add another one (like the
json structured output printer).
Basic tests and updates to the man pages are also included.

This version includes the fixes according to Austin Clements'
comments. It also changes the output from alists to plists, as these
should work just as fine on all Lisps.

Peter Feigl (5):
  Adding an S-expression structured output printer.
  Rename the -json printer functions in notmuch-reply and notmuch-show
    to     generic -sprinter functions.
  Use the S-Expression structured printer in notmuch-show,
    notmuch-reply     and notmuch-search.
  Adding tests for --format=sexp.
  Updating man pages for new S-Expression output format.

 Makefile.local            |   1 +
 devel/schemata            |   8 +-
 man/man1/notmuch-reply.1  |  14 ++-
 man/man1/notmuch-search.1 |  15 +--
 man/man1/notmuch-show.1   |  36 +++++--
 notmuch-client.h          |   8 +-
 notmuch-reply.c           |  48 ++++++----
 notmuch-search.c          |   6 +-
 notmuch-show.c            |  65 +++++++------
 sprinter-sexp.c           | 238 ++++++++++++++++++++++++++++++++++++++++++++++
 sprinter.h                |   4 +
 test/notmuch-test         |   1 +
 test/sexp                 |  48 ++++++++++
 13 files changed, 423 insertions(+), 69 deletions(-)
 create mode 100644 sprinter-sexp.c
 create mode 100755 test/sexp

-- 
1.8.0

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v3 1/5] Adding an S-expression structured output printer.
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
  2012-12-04 17:07   ` Jani Nikula
  2012-12-06  7:33   ` [PATCH v3 " Peter Feigl
@ 2012-12-06  7:33   ` Peter Feigl
  2012-12-06  7:33   ` [PATCH v3 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
                     ` (3 subsequent siblings)
  6 siblings, 0 replies; 38+ messages in thread
From: Peter Feigl @ 2012-12-06  7:33 UTC (permalink / raw)
  To: notmuch

This commit adds a structured output printer for Lisp
S-Expressions. Later commits will use this printer in notmuch search,
show and reply.

The structure is the same as json, but:
- arrays are written as lists: ("foo" "bar" "baaz" 1 2 3)
- maps are written as p-lists: (:key "value" :other-key "other-value")
- true is written as t
- false is written as nil
- null is written as nil
---
 Makefile.local  |   1 +
 sprinter-sexp.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sprinter.h      |   4 +
 3 files changed, 243 insertions(+)
 create mode 100644 sprinter-sexp.c

diff --git a/Makefile.local b/Makefile.local
index 2b91946..0db1713 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -270,6 +270,7 @@ notmuch_client_srcs =		\
 	notmuch-tag.c		\
 	notmuch-time.c		\
 	sprinter-json.c		\
+	sprinter-sexp.c		\
 	sprinter-text.c		\
 	query-string.c		\
 	mime-node.c		\
diff --git a/sprinter-sexp.c b/sprinter-sexp.c
new file mode 100644
index 0000000..8f84eed
--- /dev/null
+++ b/sprinter-sexp.c
@@ -0,0 +1,238 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2012 Peter Feigl
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Peter Feigl <peter.feigl@gmx.at>
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+
+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;
+
+    /* A flag to signify that a separator should be inserted in the
+     * output as soon as possible. */
+    notmuch_bool_t insert_separator;
+};
+
+struct sexp_state {
+    struct sexp_state *parent;
+
+    /* True if nothing has been printed in this aggregate yet.
+     * Suppresses the space before a value. */
+    notmuch_bool_t first;
+};
+
+/* Helper function to set up the stream to print a value.  If this
+ * value follows another value, prints a space. */
+static struct sprinter_sexp *
+sexp_begin_value (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if (sps->state) {
+	if (! sps->state->first) {
+	    if (sps->insert_separator) {
+		fputc ('\n', sps->stream);
+		sps->insert_separator = FALSE;
+	    } else {
+		fputc (' ', sps->stream);
+	    }
+	} else {
+	    sps->state->first = FALSE;
+	}
+    }
+    return sps;
+}
+
+/* Helper function to begin an aggregate type.  Prints the open
+ * character and pushes a new state frame. */
+static void
+sexp_begin_aggregate (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+    struct sexp_state *state = talloc (sps, struct sexp_state);
+    fputc ('(', sps->stream);
+    state->parent = sps->state;
+    state->first = TRUE;
+    sps->state = state;
+}
+
+static void
+sexp_begin_map (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp);
+}
+
+static void
+sexp_begin_list (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp);
+}
+
+static void
+sexp_end (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    struct sexp_state *state = sps->state;
+
+    fputc (')', sps->stream);
+    sps->state = state->parent;
+    talloc_free (state);
+    if (sps->state == NULL)
+	fputc ('\n', sps->stream);
+}
+
+static void
+sexp_string_len (struct sprinter *sp, const char *val, size_t len)
+{
+    /* Some characters need escaping. " and \ work fine in all Lisps,
+     * \n is not supported in CL, but all others work fine.
+     * Characters below 32 are printed as \123o (three-digit 
+     * octals), which work fine in most Schemes and Emacs. */
+    static const char *const escapes[] = {
+	['\"'] = "\\\"", ['\\'] = "\\\\",  ['\n'] = "\\n"
+    };
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputc ('"', sps->stream);
+    for (; len; ++val, --len) {
+	unsigned char ch = *val;
+	if (ch < ARRAY_SIZE (escapes) && escapes[ch])
+	    fputs (escapes[ch], sps->stream);
+	else if (ch >= 32)
+	    fputc (ch, sps->stream);
+	else
+	    fprintf (sps->stream, "\\%03oo", ch);
+    }
+    fputc ('"', sps->stream);
+}
+
+static void
+sexp_string (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+	val = "";
+    sexp_string_len (sp, val, strlen (val));
+}
+
+/* Prints a symbol, i.e. the name preceded by a colon. This should work
+ * in all Lisps, at least as a symbol, if not as a proper keyword */
+static void
+sexp_symbol (struct sprinter *sp, const char *val)
+{
+    static const char illegal_characters[] = {
+	' ', '\t', '\n'
+    };
+    unsigned int i = 0;
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if (val == NULL)
+	INTERNAL_ERROR ("illegal symbol NULL");
+
+    for(i = 0; i < ARRAY_SIZE (illegal_characters); i++) {
+	if(strchr(val, illegal_characters[i]) != NULL) {
+	    INTERNAL_ERROR ("illegal character in symbol %s", val);
+	}
+    }
+    fputc (':', sps->stream);
+    fputs (val, sps->stream);
+}
+
+static void
+sexp_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fprintf (sps->stream, "%d", val);
+}
+
+static void
+sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs (val ? "t" : "nil", sps->stream);
+}
+
+static void
+sexp_null (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs ("nil", sps->stream);
+}
+
+static void
+sexp_map_key (struct sprinter *sp, const char *key)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    if (sps->state && ! sps->state->first)
+	fputc (' ', sps->stream);
+
+    sps->state->first = FALSE;
+    sexp_symbol (sp, key);
+}
+
+static void
+sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
+{
+}
+
+static void
+sexp_separator (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    sps->insert_separator = TRUE;
+}
+
+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,
+	    .string_len = sexp_string_len,
+	    .integer = sexp_integer,
+	    .boolean = sexp_boolean,
+	    .null = sexp_null,
+	    .map_key = sexp_map_key,
+	    .separator = sexp_separator,
+	    .set_prefix = sexp_set_prefix,
+	    .is_text_printer = FALSE,
+	}
+    };
+    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 912a526..59776a9 100644
--- a/sprinter.h
+++ b/sprinter.h
@@ -70,4 +70,8 @@ sprinter_text_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.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v3 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions.
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                     ` (2 preceding siblings ...)
  2012-12-06  7:33   ` [PATCH v3 1/5] Adding an S-expression structured output printer Peter Feigl
@ 2012-12-06  7:33   ` Peter Feigl
  2012-12-06  7:33   ` [PATCH v3 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
                     ` (2 subsequent siblings)
  6 siblings, 0 replies; 38+ messages in thread
From: Peter Feigl @ 2012-12-06  7:33 UTC (permalink / raw)
  To: notmuch

All the structured output functions in notmuch-reply and notmuch-show
are renamed to a generic name (as they do not contain any json-specific
code anyway). This patch is a preparation to actually using the new
S-Expression sprinter in notmuch-reply and notmuch-show.
---
 notmuch-client.h |  8 ++++----
 notmuch-reply.c  | 43 ++++++++++++++++++++++++++-----------------
 notmuch-show.c   | 30 +++++++++++++++---------------
 3 files changed, 45 insertions(+), 36 deletions(-)

diff --git a/notmuch-client.h b/notmuch-client.h
index ae9344b..1c336dc 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -175,12 +175,12 @@ notmuch_status_t
 show_one_part (const char *filename, int part);
 
 void
-format_part_json (const void *ctx, struct sprinter *sp, mime_node_t *node,
-		  notmuch_bool_t first, notmuch_bool_t output_body);
+format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
+		      notmuch_bool_t first, notmuch_bool_t output_body);
 
 void
-format_headers_json (struct sprinter *sp, GMimeMessage *message,
-		     notmuch_bool_t reply);
+format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
+			 notmuch_bool_t reply);
 
 typedef enum {
     NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
diff --git a/notmuch-reply.c b/notmuch-reply.c
index e60a264..6103d6e 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -548,7 +548,8 @@ notmuch_reply_format_default(void *ctx,
 			     notmuch_config_t *config,
 			     notmuch_query_t *query,
 			     notmuch_show_params_t *params,
-			     notmuch_bool_t reply_all)
+			     notmuch_bool_t reply_all,
+			     unused (sprinter_t *sp))
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
@@ -587,17 +588,17 @@ notmuch_reply_format_default(void *ctx,
 }
 
 static int
-notmuch_reply_format_json(void *ctx,
-			  notmuch_config_t *config,
-			  notmuch_query_t *query,
-			  notmuch_show_params_t *params,
-			  notmuch_bool_t reply_all)
+notmuch_reply_format_sprinter(void *ctx,
+			      notmuch_config_t *config,
+			      notmuch_query_t *query,
+			      notmuch_show_params_t *params,
+			      notmuch_bool_t reply_all,
+			      sprinter_t *sp)
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
     notmuch_message_t *message;
     mime_node_t *node;
-    sprinter_t *sp;
 
     if (notmuch_query_count_messages (query) != 1) {
 	fprintf (stderr, "Error: search term did not match precisely one message.\n");
@@ -613,18 +614,17 @@ notmuch_reply_format_json(void *ctx,
     if (!reply)
 	return 1;
 
-    sp = sprinter_json_create (ctx, stdout);
     sp->begin_map (sp);
 
     /* The headers of the reply message we've created */
     sp->map_key (sp, "reply-headers");
-    format_headers_json (sp, reply, TRUE);
+    format_headers_sprinter (sp, reply, TRUE);
     g_object_unref (G_OBJECT (reply));
     reply = NULL;
 
     /* Start the original */
     sp->map_key (sp, "original");
-    format_part_json (ctx, sp, node, TRUE, TRUE);
+    format_part_sprinter (ctx, sp, node, TRUE, TRUE);
 
     /* End */
     sp->end (sp);
@@ -639,7 +639,8 @@ notmuch_reply_format_headers_only(void *ctx,
 				  notmuch_config_t *config,
 				  notmuch_query_t *query,
 				  unused (notmuch_show_params_t *params),
-				  notmuch_bool_t reply_all)
+				  notmuch_bool_t reply_all,
+				  unused (sprinter_t *sp))
 {
     GMimeMessage *reply;
     notmuch_messages_t *messages;
@@ -707,7 +708,12 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     notmuch_query_t *query;
     char *query_string;
     int opt_index, ret = 0;
-    int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
+    int (*reply_format_func)(void *ctx,
+			     notmuch_config_t *config,
+			     notmuch_query_t *query,
+			     notmuch_show_params_t *params,
+			     notmuch_bool_t reply_all,
+			     struct sprinter *sp);
     notmuch_show_params_t params = {
 	.part = -1,
 	.crypto = {
@@ -717,6 +723,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     };
     int format = FORMAT_DEFAULT;
     int reply_all = TRUE;
+    struct sprinter *sp = NULL;
 
     notmuch_opt_desc_t options[] = {
 	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
@@ -738,12 +745,14 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	return 1;
     }
 
-    if (format == FORMAT_HEADERS_ONLY)
+    if (format == FORMAT_HEADERS_ONLY) {
 	reply_format_func = notmuch_reply_format_headers_only;
-    else if (format == FORMAT_JSON)
-	reply_format_func = notmuch_reply_format_json;
-    else
+    } else if (format == FORMAT_JSON) {
+	reply_format_func = notmuch_reply_format_sprinter;
+	sp = sprinter_json_create (ctx, stdout);
+    } else {
 	reply_format_func = notmuch_reply_format_default;
+    }
 
     config = notmuch_config_open (ctx, NULL, NULL);
     if (config == NULL)
@@ -770,7 +779,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	return 1;
     }
 
-    if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
+    if (reply_format_func (ctx, config, query, &params, reply_all, sp) != 0)
 	return 1;
 
     notmuch_crypto_cleanup (&params.crypto);
diff --git a/notmuch-show.c b/notmuch-show.c
index 2fa2292..38c621f 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -32,12 +32,12 @@ static const notmuch_show_format_t format_text = {
 };
 
 static notmuch_status_t
-format_part_json_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp, mime_node_t *node,
 			int indent, const notmuch_show_params_t *params);
 
 static const notmuch_show_format_t format_json = {
     .new_sprinter = sprinter_json_create,
-    .part = format_part_json_entry,
+    .part = format_part_sprinter_entry,
 };
 
 static notmuch_status_t
@@ -108,7 +108,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
 /* Emit a sequence of key/value pairs for the metadata of message.
  * The caller should begin a map before calling this. */
 static void
-format_message_json (sprinter_t *sp, notmuch_message_t *message)
+format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
 {
     /* Any changes to the JSON format should be reflected in the file
      * devel/schemata. */
@@ -208,7 +208,7 @@ _is_from_line (const char *line)
 }
 
 void
-format_headers_json (sprinter_t *sp, GMimeMessage *message,
+format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
 		     notmuch_bool_t reply)
 {
     /* Any changes to the JSON format should be reflected in the file
@@ -363,7 +363,7 @@ signer_status_to_string (GMimeSignerStatus x)
 
 #ifdef GMIME_ATLEAST_26
 static void
-format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
     /* Any changes to the JSON format should be reflected in the file
      * devel/schemata. */
@@ -438,7 +438,7 @@ format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
 }
 #else
 static void
-format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
     const GMimeSignatureValidity* validity = node->sig_validity;
 
@@ -595,7 +595,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
 }
 
 void
-format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
+format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
 		  notmuch_bool_t first, notmuch_bool_t output_body)
 {
     /* Any changes to the JSON format should be reflected in the file
@@ -603,15 +603,15 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->envelope_file) {
 	sp->begin_map (sp);
-	format_message_json (sp, node->envelope_file);
+	format_message_sprinter (sp, node->envelope_file);
 
 	sp->map_key (sp, "headers");
-	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
+	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
 
 	if (output_body) {
 	    sp->map_key (sp, "body");
 	    sp->begin_list (sp);
-	    format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE);
+	    format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE);
 	    sp->end (sp);
 	}
 	sp->end (sp);
@@ -646,7 +646,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 
     if (node->verify_attempted) {
 	sp->map_key (sp, "sigstatus");
-	format_part_sigstatus_json (sp, node);
+	format_part_sigstatus_sprinter (sp, node);
     }
 
     sp->map_key (sp, "content-type");
@@ -698,7 +698,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 	sp->begin_map (sp);
 
 	sp->map_key (sp, "headers");
-	format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
+	format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
 
 	sp->map_key (sp, "body");
 	sp->begin_list (sp);
@@ -706,7 +706,7 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
     }
 
     for (i = 0; i < node->nchildren; i++)
-	format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
+	format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
 
     /* Close content structures */
     for (i = 0; i < nclose; i++)
@@ -716,11 +716,11 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
 }
 
 static notmuch_status_t
-format_part_json_entry (const void *ctx, sprinter_t *sp,
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
 			mime_node_t *node, unused (int indent),
 			const notmuch_show_params_t *params)
 {
-    format_part_json (ctx, sp, node, TRUE, params->output_body);
+    format_part_sprinter (ctx, sp, node, TRUE, params->output_body);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v3 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                     ` (3 preceding siblings ...)
  2012-12-06  7:33   ` [PATCH v3 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
@ 2012-12-06  7:33   ` Peter Feigl
  2012-12-06  7:33   ` [PATCH v3 4/5] Adding tests for --format=sexp Peter Feigl
  2012-12-06  7:33   ` [PATCH v3 5/5] Updating man pages for new S-Expression output format Peter Feigl
  6 siblings, 0 replies; 38+ messages in thread
From: Peter Feigl @ 2012-12-06  7:33 UTC (permalink / raw)
  To: notmuch

This patch uses the new S-Expression printer in the notmuch CLI (show,
search and reply). You can now use --format=sexp for any of them.
---
 devel/schemata   |  8 +++++++-
 notmuch-reply.c  |  5 +++++
 notmuch-search.c |  6 +++++-
 notmuch-show.c   | 35 +++++++++++++++++++++++------------
 4 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/devel/schemata b/devel/schemata
index e44da71..01181d4 100644
--- a/devel/schemata
+++ b/devel/schemata
@@ -1,5 +1,5 @@
 This file describes the schemata used for notmuch's structured output
-format (currently JSON).
+format (currently JSON and S-Expressions).
 
 []'s indicate lists.  List items can be marked with a '?', meaning
 they are optional; or a '*', meaning there can be zero or more of that
@@ -8,6 +8,12 @@ values.  An object field marked '?' is optional.  |'s indicate
 alternates (e.g., int|string means something can be an int or a
 string).
 
+For S-Expression output, lists are printed delimited by () instead of
+[]. Objects are printed as p-lists, i.e. lists where the keys and values
+are interleaved. Keys are printed as keywords (symbols preceded by a
+colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as
+nil, true as t and false as nil.
+
 Common non-terminals
 --------------------
 
diff --git a/notmuch-reply.c b/notmuch-reply.c
index 6103d6e..5f3e46c 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -697,6 +697,7 @@ notmuch_reply_format_headers_only(void *ctx,
 enum {
     FORMAT_DEFAULT,
     FORMAT_JSON,
+    FORMAT_SEXP,
     FORMAT_HEADERS_ONLY,
 };
 
@@ -729,6 +730,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
 	{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
 	  (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
 				  { "json", FORMAT_JSON },
+				  { "sexp", FORMAT_SEXP },
 				  { "headers-only", FORMAT_HEADERS_ONLY },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
@@ -750,6 +752,9 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
     } else if (format == FORMAT_JSON) {
 	reply_format_func = notmuch_reply_format_sprinter;
 	sp = sprinter_json_create (ctx, stdout);
+    } else if (format == FORMAT_SEXP) {
+	reply_format_func = notmuch_reply_format_sprinter;
+	sp = sprinter_sexp_create (ctx, stdout);
     } else {
 	reply_format_func = notmuch_reply_format_default;
     }
diff --git a/notmuch-search.c b/notmuch-search.c
index 830c4e4..6218622 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -305,7 +305,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[] = {
@@ -315,6 +315,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "text", NOTMUCH_FORMAT_TEXT },
 				  { 0, 0 } } },
 	{ NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
@@ -347,6 +348,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;
     default:
 	/* this should never happen */
 	INTERNAL_ERROR("no output format selected");
diff --git a/notmuch-show.c b/notmuch-show.c
index 38c621f..2bd09f3 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -40,6 +40,11 @@ static const notmuch_show_format_t format_json = {
     .part = format_part_sprinter_entry,
 };
 
+static const notmuch_show_format_t format_sexp = {
+    .new_sprinter = sprinter_sexp_create,
+    .part = format_part_sprinter_entry,
+};
+
 static notmuch_status_t
 format_part_mbox (const void *ctx, sprinter_t *sp, mime_node_t *node,
 		  int indent, const notmuch_show_params_t *params);
@@ -110,8 +115,8 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
 static void
 format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
 {
-    /* Any changes to the JSON format should be reflected in the file
-     * devel/schemata. */
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
 
     void *local = talloc_new (NULL);
     notmuch_tags_t *tags;
@@ -211,8 +216,8 @@ void
 format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
 		     notmuch_bool_t reply)
 {
-    /* Any changes to the JSON format should be reflected in the file
-     * devel/schemata. */
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
 
     InternetAddressList *recipients;
     const char *recipients_string;
@@ -365,8 +370,8 @@ signer_status_to_string (GMimeSignerStatus x)
 static void
 format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
 {
-    /* Any changes to the JSON format should be reflected in the file
-     * devel/schemata. */
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
 
     GMimeSignatureList *siglist = node->sig_list;
 
@@ -598,8 +603,8 @@ void
 format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
 		  notmuch_bool_t first, notmuch_bool_t output_body)
 {
-    /* Any changes to the JSON format should be reflected in the file
-     * devel/schemata. */
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
 
     if (node->envelope_file) {
 	sp->begin_map (sp);
@@ -1012,6 +1017,7 @@ do_show (void *ctx,
 enum {
     NOTMUCH_FORMAT_NOT_SPECIFIED,
     NOTMUCH_FORMAT_JSON,
+    NOTMUCH_FORMAT_SEXP,
     NOTMUCH_FORMAT_TEXT,
     NOTMUCH_FORMAT_MBOX,
     NOTMUCH_FORMAT_RAW
@@ -1056,6 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	{ NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
 	  (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
 				  { "text", NOTMUCH_FORMAT_TEXT },
+				  { "sexp", NOTMUCH_FORMAT_SEXP },
 				  { "mbox", NOTMUCH_FORMAT_MBOX },
 				  { "raw", NOTMUCH_FORMAT_RAW },
 				  { 0, 0 } } },
@@ -1100,6 +1107,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
     case NOTMUCH_FORMAT_TEXT:
 	format = &format_text;
 	break;
+    case NOTMUCH_FORMAT_SEXP:
+	format = &format_sexp;
+	break;
     case NOTMUCH_FORMAT_MBOX:
 	if (params.part > 0) {
 	    fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
@@ -1118,9 +1128,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	break;
     }
 
-    /* Default is entire-thread = FALSE except for format=json. */
+    /* Default is entire-thread = FALSE except for format=json and format=sexp. */
     if (entire_thread == ENTIRE_THREAD_DEFAULT) {
-	if (format == &format_json)
+	if (format == &format_json || format == &format_sexp)
 	    entire_thread = ENTIRE_THREAD_TRUE;
 	else
 	    entire_thread = ENTIRE_THREAD_FALSE;
@@ -1131,8 +1141,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 	    fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
 	    params.output_body = TRUE;
 	} else {
-	    if (format != &format_json)
-		fprintf (stderr, "Warning: --body=false only implemented for format=json\n");
+	    if (format != &format_json && format != &format_sexp)
+		fprintf (stderr,
+			 "Warning: --body=false only implemented for format=json and format=sexp\n");
 	}
     }
 
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v3 4/5] Adding tests for --format=sexp.
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                     ` (4 preceding siblings ...)
  2012-12-06  7:33   ` [PATCH v3 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
@ 2012-12-06  7:33   ` Peter Feigl
  2012-12-06  8:18     ` Mark Walters
  2012-12-06  7:33   ` [PATCH v3 5/5] Updating man pages for new S-Expression output format Peter Feigl
  6 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-12-06  7:33 UTC (permalink / raw)
  To: notmuch

Add basic tests, the same as for json, for the S-Expression output
format.
---
 test/notmuch-test |  1 +
 test/sexp         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)
 create mode 100755 test/sexp

diff --git a/test/notmuch-test b/test/notmuch-test
index a6ef34f..ca9c3dc 100755
--- a/test/notmuch-test
+++ b/test/notmuch-test
@@ -31,6 +31,7 @@ TESTS="
   excludes
   tagging
   json
+  sexp
   text
   multipart
   thread-naming
diff --git a/test/sexp b/test/sexp
new file mode 100755
index 0000000..335844e
--- /dev/null
+++ b/test/sexp
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+test_description="--format=sexp output"
+. ./test-lib.sh
+
+test_begin_subtest "Show message: sexp"
+add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
+output=$(notmuch show --format=sexp "sexp-show-message")
+test_expect_equal "$output" "((((:id \"msg-001@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
+
+# This should be the same output as above.
+test_begin_subtest "Show message: sexp --body=true"
+output=$(notmuch show --format=sexp --body=true "sexp-show-message")
+test_expect_equal "$output" "((((:id \"msg-001@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
+
+test_begin_subtest "Show message: sexp --body=false"
+output=$(notmuch show --format=sexp --body=false "sexp-show-message")
+test_expect_equal "$output" "((((:id \"msg-001@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))"
+
+test_begin_subtest "Search message: sexp"
+add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
+output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
+test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :tags (\"inbox\" \"unread\")))"
+
+test_begin_subtest "Show message: sexp, utf-8"
+add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
+output=$(notmuch show --format=sexp "jsön-show-méssage")
+test_expect_equal "$output" "((((:id \"msg-003@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-003\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-utf8-body-sübjéct\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"jsön-show-méssage\n\"))) ())))"
+
+test_begin_subtest "Show message: sexp, inline attachment filename"
+subject='sexp-show-inline-attachment-filename'
+id="sexp-show-inline-attachment-filename@notmuchmail.org"
+emacs_deliver_message \
+    "$subject" \
+    'This is a test message with inline attachment with a filename' \
+    "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
+     (message-goto-eoh)
+     (insert \"Message-ID: <$id>\n\")"
+output=$(notmuch show --format=sexp "id:$id")
+filename=$(notmuch search --output=files "id:$id")
+test_expect_equal "$output" "((((:id \"sexp-show-inline-attachment-filename@notmuchmail.org\" :match t :excluded nil :filename \"$filename\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :filename \"README\"))))) ())))"
+
+test_begin_subtest "Search message: sexp, utf-8"
+add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
+output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
+test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :tags (\"inbox\" \"unread\")))"
+
+
+test_done
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v3 5/5] Updating man pages for new S-Expression output format.
  2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                     ` (5 preceding siblings ...)
  2012-12-06  7:33   ` [PATCH v3 4/5] Adding tests for --format=sexp Peter Feigl
@ 2012-12-06  7:33   ` Peter Feigl
  2012-12-06  8:19     ` Mark Walters
  6 siblings, 1 reply; 38+ messages in thread
From: Peter Feigl @ 2012-12-06  7:33 UTC (permalink / raw)
  To: notmuch

Add sections about the new S-Expression output format (--format=sexp) to
the notmuch-search, notmuch-reply and notmuch-show man pages.
---
 man/man1/notmuch-reply.1  | 14 ++++++++++----
 man/man1/notmuch-search.1 | 15 ++++++++-------
 man/man1/notmuch-show.1   | 36 ++++++++++++++++++++++++++++--------
 3 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1
index d264060..f71d764 100644
--- a/man/man1/notmuch-reply.1
+++ b/man/man1/notmuch-reply.1
@@ -37,7 +37,7 @@ Supported options for
 include
 .RS
 .TP 4
-.BR \-\-format= ( default | json | headers\-only )
+.BR \-\-format= ( default | json | sexp | headers\-only )
 .RS
 .TP 4
 .BR default
@@ -48,6 +48,11 @@ Produces JSON output containing headers for a reply message and the
 contents of the original message. This output can be used by a client
 to create a reply message intelligently.
 .TP
+.BR sexp
+Produces S-Expression output containing headers for a reply message and
+the contents of the original message. This output can be used by a client
+to create a reply message intelligently.
+.TP
 .BR headers\-only
 Only produces In\-Reply\-To, References, To, Cc, and Bcc headers.
 .RE
@@ -74,8 +79,8 @@ user's addresses.
 
 Decrypt any MIME encrypted parts found in the selected content
 (ie. "multipart/encrypted" parts). Status of the decryption will be
-reported (currently only supported with --format=json) and the
-multipart/encrypted part will be replaced by the decrypted
+reported (currently only supported with --format=json and --format=sexp)
+and the multipart/encrypted part will be replaced by the decrypted
 content.
 .RE
 
@@ -89,7 +94,8 @@ id:<message-id>), but it can be useful to reply to several messages at
 once. For example, when a series of patches are sent in a single
 thread, replying to the entire thread allows for the reply to comment
 on issues found in multiple patches. The default format supports
-replying to multiple messages at once, but the JSON format does not.
+replying to multiple messages at once, but the JSON and S-Expression
+format does not.
 .RE
 .RE
 
diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1
index 6ccd3b8..0aff348 100644
--- a/man/man1/notmuch-search.1
+++ b/man/man1/notmuch-search.1
@@ -25,9 +25,9 @@ Supported options for
 include
 .RS 4
 .TP 4
-.BR \-\-format= ( json | text )
+.BR \-\-format= ( json | sexp | text )
 
-Presents the results in either JSON or plain-text (default).
+Presents the results in either JSON, S-Expressions or plain-text (default).
 .RE
 
 .RS 4
@@ -49,7 +49,7 @@ the authors of the thread and the subject.
 
 Output the thread IDs of all threads with any message matching the
 search terms, either one per line (\-\-format=text) or as a JSON array
-(\-\-format=json).
+(\-\-format=json) or an S-Expression list (\-\-format=sexp).
 .RE
 .RS 4
 .TP 4
@@ -57,22 +57,23 @@ search terms, either one per line (\-\-format=text) or as a JSON array
 
 Output the message IDs of all messages matching the search terms,
 either one per line (\-\-format=text) or as a JSON array
-(\-\-format=json).
+(\-\-format=json) or as an S-Expression list (\-\-format=sexp).
 .RE
 .RS 4
 .TP 4
 .B files
 
 Output the filenames of all messages matching the search terms, either
-one per line (\-\-format=text) or as a JSON array (\-\-format=json).
+one per line (\-\-format=text) or as a JSON array (\-\-format=json) or
+as an S-Expression list (\-\-format=sexp).
 .RE
 .RS 4
 .TP 4
 .B tags
 
 Output all tags that appear on any message matching the search terms,
-either one per line (\-\-format=text) or as a JSON array
-(\-\-format=json).
+either one per line (\-\-format=text) or as a JSON array (\-\-format=json)
+or as an S-Expression list (\-\-format=sexp).
 .RE
 .RE
 
diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1
index 4481f21..bd41c48 100644
--- a/man/man1/notmuch-show.1
+++ b/man/man1/notmuch-show.1
@@ -31,12 +31,14 @@ If true,
 outputs all messages in the thread of any message matching the search
 terms; if false, it outputs only the matching messages. For
 .B --format=json
+and
+.B --format=sexp
 this defaults to true.  For other formats, this defaults to false.
 .RE
 
 .RS 4
 .TP 4
-.B \-\-format=(text|json|mbox|raw)
+.B \-\-format=(text|json|sexp|mbox|raw)
 
 .RS 4
 .TP 4
@@ -60,11 +62,29 @@ format is more robust than the text format for automated
 processing. The nested structure of multipart MIME messages is
 reflected in nested JSON output. By default JSON output includes all
 messages in a matching thread; that is, by default,
+
 .B \-\-format=json
 sets
 .B "\-\-entire\-thread"
 The caller can disable this behaviour by setting
 .B \-\-entire\-thread=false
+.RE
+.RS 4
+.TP 4
+.B sexp
+
+The output is formatted as an S-Expression (sexp). This
+format is more robust than the text format for automated
+processing. The nested structure of multipart MIME messages is
+reflected in nested S-Expression output. By default,
+S-Expression output includes all messages in a matching thread;
+that is, by default,
+
+.B \-\-format=sexp
+sets
+.B "\-\-entire\-thread"
+The caller can disable this behaviour by setting
+.B \-\-entire\-thread=false
 
 .RE
 .RS 4
@@ -113,7 +133,7 @@ message.
 Output the single decoded MIME part N of a single message.  The search
 terms must match only a single message.  Message parts are numbered in
 a depth-first walk of the message MIME structure, and are identified
-in the 'json' or 'text' output formats.
+in the 'json', 'sexp' or 'text' output formats.
 .RE
 
 .RS 4
@@ -123,8 +143,8 @@ in the 'json' or 'text' output formats.
 Compute and report the validity of any MIME cryptographic signatures
 found in the selected content (ie. "multipart/signed" parts). Status
 of the signature will be reported (currently only supported with
---format=json), and the multipart/signed part will be replaced by the
-signed data.
+--format=json and --format=sexp), and the multipart/signed part
+will be replaced by the signed data.
 .RE
 
 .RS 4
@@ -133,9 +153,9 @@ signed data.
 
 Decrypt any MIME encrypted parts found in the selected content
 (ie. "multipart/encrypted" parts). Status of the decryption will be
-reported (currently only supported with --format=json) and the
-multipart/encrypted part will be replaced by the decrypted
-content.  Implies --verify.
+reported (currently only supported with --format=json and
+--format=sexp) and the multipart/encrypted part will be replaced
+by the decrypted content.  Implies --verify.
 .RE
 
 .RS 4
@@ -166,7 +186,7 @@ If true (the default)
 includes the bodies of the messages in the output; if false,
 bodies are omitted.
 .B --body=false
-is only implemented for the json format and it is incompatible with
+is only implemented for the json and sexp formats and it is incompatible with
 .B --part > 0.
 
 This is useful if the caller only needs the headers as body-less
-- 
1.8.0

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH v3 4/5] Adding tests for --format=sexp.
  2012-12-06  7:33   ` [PATCH v3 4/5] Adding tests for --format=sexp Peter Feigl
@ 2012-12-06  8:18     ` Mark Walters
  0 siblings, 0 replies; 38+ messages in thread
From: Mark Walters @ 2012-12-06  8:18 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Thu, 06 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> Add basic tests, the same as for json, for the S-Expression output
> format.
> ---
>  test/notmuch-test |  1 +
>  test/sexp         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 49 insertions(+)
>  create mode 100755 test/sexp
>
> diff --git a/test/notmuch-test b/test/notmuch-test
> index a6ef34f..ca9c3dc 100755
> --- a/test/notmuch-test
> +++ b/test/notmuch-test
> @@ -31,6 +31,7 @@ TESTS="
>    excludes
>    tagging
>    json
> +  sexp
>    text
>    multipart
>    thread-naming
> diff --git a/test/sexp b/test/sexp
> new file mode 100755
> index 0000000..335844e
> --- /dev/null
> +++ b/test/sexp
> @@ -0,0 +1,48 @@
> +#!/usr/bin/env bash
> +test_description="--format=sexp output"
> +. ./test-lib.sh
> +
> +test_begin_subtest "Show message: sexp"
> +add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
> +output=$(notmuch show --format=sexp "sexp-show-message")
> +test_expect_equal "$output" "((((:id \"msg-001@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"

The :filename above is hardcoded (so will fail for most of us). I know
there are some variables/functions for things like that but I am not
sure exactly which bits need to be wrapped. I think message-id and
thread-id are often a problem too.

Best wishes

Mark


> +
> +# This should be the same output as above.
> +test_begin_subtest "Show message: sexp --body=true"
> +output=$(notmuch show --format=sexp --body=true "sexp-show-message")
> +test_expect_equal "$output" "((((:id \"msg-001@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"sexp-show-message\n\"))) ())))"
> +
> +test_begin_subtest "Show message: sexp --body=false"
> +output=$(notmuch show --format=sexp --body=false "sexp-show-message")
> +test_expect_equal "$output" "((((:id \"msg-001@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-001\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-subject\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Bcc \"test_suite+bcc@notmuchmail.org\" :Reply-To \"test_suite+replyto@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\")) ())))"
> +
> +test_begin_subtest "Search message: sexp"
> +add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
> +output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
> +test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :tags (\"inbox\" \"unread\")))"
> +
> +test_begin_subtest "Show message: sexp, utf-8"
> +add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""
> +output=$(notmuch show --format=sexp "jsön-show-méssage")
> +test_expect_equal "$output" "((((:id \"msg-003@notmuch-test-suite\" :match t :excluded nil :filename \"/home/nex/notmuch-sexp/test/tmp.sexp/mail/msg-003\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-utf8-body-sübjéct\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"jsön-show-méssage\n\"))) ())))"
> +
> +test_begin_subtest "Show message: sexp, inline attachment filename"
> +subject='sexp-show-inline-attachment-filename'
> +id="sexp-show-inline-attachment-filename@notmuchmail.org"
> +emacs_deliver_message \
> +    "$subject" \
> +    'This is a test message with inline attachment with a filename' \
> +    "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
> +     (message-goto-eoh)
> +     (insert \"Message-ID: <$id>\n\")"
> +output=$(notmuch show --format=sexp "id:$id")
> +filename=$(notmuch search --output=files "id:$id")
> +test_expect_equal "$output" "((((:id \"sexp-show-inline-attachment-filename@notmuchmail.org\" :match t :excluded nil :filename \"$filename\" :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :filename \"README\"))))) ())))"
> +
> +test_begin_subtest "Search message: sexp, utf-8"
> +add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
> +output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
> +test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :tags (\"inbox\" \"unread\")))"
> +
> +
> +test_done
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v3 5/5] Updating man pages for new S-Expression output format.
  2012-12-06  7:33   ` [PATCH v3 5/5] Updating man pages for new S-Expression output format Peter Feigl
@ 2012-12-06  8:19     ` Mark Walters
  0 siblings, 0 replies; 38+ messages in thread
From: Mark Walters @ 2012-12-06  8:19 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Thu, 06 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> Add sections about the new S-Expression output format (--format=sexp) to
> the notmuch-search, notmuch-reply and notmuch-show man pages.
> ---
>  man/man1/notmuch-reply.1  | 14 ++++++++++----
>  man/man1/notmuch-search.1 | 15 ++++++++-------
>  man/man1/notmuch-show.1   | 36 ++++++++++++++++++++++++++++--------
>  3 files changed, 46 insertions(+), 19 deletions(-)
>
> diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1
> index d264060..f71d764 100644
> --- a/man/man1/notmuch-reply.1
> +++ b/man/man1/notmuch-reply.1
> @@ -37,7 +37,7 @@ Supported options for
>  include
>  .RS
>  .TP 4
> -.BR \-\-format= ( default | json | headers\-only )
> +.BR \-\-format= ( default | json | sexp | headers\-only )
>  .RS
>  .TP 4
>  .BR default
> @@ -48,6 +48,11 @@ Produces JSON output containing headers for a reply message and the
>  contents of the original message. This output can be used by a client
>  to create a reply message intelligently.
>  .TP
> +.BR sexp
> +Produces S-Expression output containing headers for a reply message and
> +the contents of the original message. This output can be used by a client
> +to create a reply message intelligently.
> +.TP
>  .BR headers\-only
>  Only produces In\-Reply\-To, References, To, Cc, and Bcc headers.
>  .RE
> @@ -74,8 +79,8 @@ user's addresses.
>  
>  Decrypt any MIME encrypted parts found in the selected content
>  (ie. "multipart/encrypted" parts). Status of the decryption will be
> -reported (currently only supported with --format=json) and the
> -multipart/encrypted part will be replaced by the decrypted
> +reported (currently only supported with --format=json and --format=sexp)
> +and the multipart/encrypted part will be replaced by the decrypted
>  content.
>  .RE
>  
> @@ -89,7 +94,8 @@ id:<message-id>), but it can be useful to reply to several messages at
>  once. For example, when a series of patches are sent in a single
>  thread, replying to the entire thread allows for the reply to comment
>  on issues found in multiple patches. The default format supports
> -replying to multiple messages at once, but the JSON format does not.
> +replying to multiple messages at once, but the JSON and S-Expression
> +format does not.

Totally trivial but should be "do not".

MW


>  .RE
>  .RE
>  
> diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1
> index 6ccd3b8..0aff348 100644
> --- a/man/man1/notmuch-search.1
> +++ b/man/man1/notmuch-search.1
> @@ -25,9 +25,9 @@ Supported options for
>  include
>  .RS 4
>  .TP 4
> -.BR \-\-format= ( json | text )
> +.BR \-\-format= ( json | sexp | text )
>  
> -Presents the results in either JSON or plain-text (default).
> +Presents the results in either JSON, S-Expressions or plain-text (default).
>  .RE
>  
>  .RS 4
> @@ -49,7 +49,7 @@ the authors of the thread and the subject.
>  
>  Output the thread IDs of all threads with any message matching the
>  search terms, either one per line (\-\-format=text) or as a JSON array
> -(\-\-format=json).
> +(\-\-format=json) or an S-Expression list (\-\-format=sexp).
>  .RE
>  .RS 4
>  .TP 4
> @@ -57,22 +57,23 @@ search terms, either one per line (\-\-format=text) or as a JSON array
>  
>  Output the message IDs of all messages matching the search terms,
>  either one per line (\-\-format=text) or as a JSON array
> -(\-\-format=json).
> +(\-\-format=json) or as an S-Expression list (\-\-format=sexp).
>  .RE
>  .RS 4
>  .TP 4
>  .B files
>  
>  Output the filenames of all messages matching the search terms, either
> -one per line (\-\-format=text) or as a JSON array (\-\-format=json).
> +one per line (\-\-format=text) or as a JSON array (\-\-format=json) or
> +as an S-Expression list (\-\-format=sexp).
>  .RE
>  .RS 4
>  .TP 4
>  .B tags
>  
>  Output all tags that appear on any message matching the search terms,
> -either one per line (\-\-format=text) or as a JSON array
> -(\-\-format=json).
> +either one per line (\-\-format=text) or as a JSON array (\-\-format=json)
> +or as an S-Expression list (\-\-format=sexp).
>  .RE
>  .RE
>  
> diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1
> index 4481f21..bd41c48 100644
> --- a/man/man1/notmuch-show.1
> +++ b/man/man1/notmuch-show.1
> @@ -31,12 +31,14 @@ If true,
>  outputs all messages in the thread of any message matching the search
>  terms; if false, it outputs only the matching messages. For
>  .B --format=json
> +and
> +.B --format=sexp
>  this defaults to true.  For other formats, this defaults to false.
>  .RE
>  
>  .RS 4
>  .TP 4
> -.B \-\-format=(text|json|mbox|raw)
> +.B \-\-format=(text|json|sexp|mbox|raw)
>  
>  .RS 4
>  .TP 4
> @@ -60,11 +62,29 @@ format is more robust than the text format for automated
>  processing. The nested structure of multipart MIME messages is
>  reflected in nested JSON output. By default JSON output includes all
>  messages in a matching thread; that is, by default,
> +
>  .B \-\-format=json
>  sets
>  .B "\-\-entire\-thread"
>  The caller can disable this behaviour by setting
>  .B \-\-entire\-thread=false
> +.RE
> +.RS 4
> +.TP 4
> +.B sexp
> +
> +The output is formatted as an S-Expression (sexp). This
> +format is more robust than the text format for automated
> +processing. The nested structure of multipart MIME messages is
> +reflected in nested S-Expression output. By default,
> +S-Expression output includes all messages in a matching thread;
> +that is, by default,
> +
> +.B \-\-format=sexp
> +sets
> +.B "\-\-entire\-thread"
> +The caller can disable this behaviour by setting
> +.B \-\-entire\-thread=false
>  
>  .RE
>  .RS 4
> @@ -113,7 +133,7 @@ message.
>  Output the single decoded MIME part N of a single message.  The search
>  terms must match only a single message.  Message parts are numbered in
>  a depth-first walk of the message MIME structure, and are identified
> -in the 'json' or 'text' output formats.
> +in the 'json', 'sexp' or 'text' output formats.
>  .RE
>  
>  .RS 4
> @@ -123,8 +143,8 @@ in the 'json' or 'text' output formats.
>  Compute and report the validity of any MIME cryptographic signatures
>  found in the selected content (ie. "multipart/signed" parts). Status
>  of the signature will be reported (currently only supported with
> ---format=json), and the multipart/signed part will be replaced by the
> -signed data.
> +--format=json and --format=sexp), and the multipart/signed part
> +will be replaced by the signed data.
>  .RE
>  
>  .RS 4
> @@ -133,9 +153,9 @@ signed data.
>  
>  Decrypt any MIME encrypted parts found in the selected content
>  (ie. "multipart/encrypted" parts). Status of the decryption will be
> -reported (currently only supported with --format=json) and the
> -multipart/encrypted part will be replaced by the decrypted
> -content.  Implies --verify.
> +reported (currently only supported with --format=json and
> +--format=sexp) and the multipart/encrypted part will be replaced
> +by the decrypted content.  Implies --verify.
>  .RE
>  
>  .RS 4
> @@ -166,7 +186,7 @@ If true (the default)
>  includes the bodies of the messages in the output; if false,
>  bodies are omitted.
>  .B --body=false
> -is only implemented for the json format and it is incompatible with
> +is only implemented for the json and sexp formats and it is incompatible with
>  .B --part > 0.
>  
>  This is useful if the caller only needs the headers as body-less
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v3 0/5] New output format sexp (Lisp S-Expressions)
  2012-12-06  7:33   ` [PATCH v3 " Peter Feigl
@ 2012-12-06  8:22     ` Mark Walters
  2012-12-07  2:54     ` Jameson Graef Rollins
  1 sibling, 0 replies; 38+ messages in thread
From: Mark Walters @ 2012-12-06  8:22 UTC (permalink / raw)
  To: Peter Feigl, notmuch


On Thu, 06 Dec 2012, Peter Feigl <craven@gmx.net> wrote:
> This patch series adds a new output format "sexp" to notmuch-reply,
> notmuch-show and notmuch-search. These are useful for the Android mobile
> client and perhaps other Lisp programs as well.
> After the switch to a generic structured output printer, which was
> committed some months ago, these patches just add another one (like the
> json structured output printer).
> Basic tests and updates to the man pages are also included.
>
> This version includes the fixes according to Austin Clements'
> comments. It also changes the output from alists to plists, as these
> should work just as fine on all Lisps.

This is looking very nice: I haven't reviewed the first patch (the
sprinting itself) but the others look good (modulo the two small
points). I can confirm that the output works in notmuch show (with a two
line tweak to use s-expressions).

Best wishes

Mark


>
> Peter Feigl (5):
>   Adding an S-expression structured output printer.
>   Rename the -json printer functions in notmuch-reply and notmuch-show
>     to     generic -sprinter functions.
>   Use the S-Expression structured printer in notmuch-show,
>     notmuch-reply     and notmuch-search.
>   Adding tests for --format=sexp.
>   Updating man pages for new S-Expression output format.
>
>  Makefile.local            |   1 +
>  devel/schemata            |   8 +-
>  man/man1/notmuch-reply.1  |  14 ++-
>  man/man1/notmuch-search.1 |  15 +--
>  man/man1/notmuch-show.1   |  36 +++++--
>  notmuch-client.h          |   8 +-
>  notmuch-reply.c           |  48 ++++++----
>  notmuch-search.c          |   6 +-
>  notmuch-show.c            |  65 +++++++------
>  sprinter-sexp.c           | 238 ++++++++++++++++++++++++++++++++++++++++++++++
>  sprinter.h                |   4 +
>  test/notmuch-test         |   1 +
>  test/sexp                 |  48 ++++++++++
>  13 files changed, 423 insertions(+), 69 deletions(-)
>  create mode 100644 sprinter-sexp.c
>  create mode 100755 test/sexp
>
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v3 0/5] New output format sexp (Lisp S-Expressions)
  2012-12-06  7:33   ` [PATCH v3 " Peter Feigl
  2012-12-06  8:22     ` Mark Walters
@ 2012-12-07  2:54     ` Jameson Graef Rollins
  1 sibling, 0 replies; 38+ messages in thread
From: Jameson Graef Rollins @ 2012-12-07  2:54 UTC (permalink / raw)
  To: Peter Feigl, notmuch

[-- Attachment #1: Type: text/plain, Size: 2145 bytes --]

Hi, Peter.  For future reference, I think we prefer to have new versions
of patch series in new threads.  I think it makes things much easier to
keep track of.

jamie.

On Wed, Dec 05 2012, Peter Feigl <craven@gmx.net> wrote:
> This patch series adds a new output format "sexp" to notmuch-reply,
> notmuch-show and notmuch-search. These are useful for the Android mobile
> client and perhaps other Lisp programs as well.
> After the switch to a generic structured output printer, which was
> committed some months ago, these patches just add another one (like the
> json structured output printer).
> Basic tests and updates to the man pages are also included.
>
> This version includes the fixes according to Austin Clements'
> comments. It also changes the output from alists to plists, as these
> should work just as fine on all Lisps.
>
> Peter Feigl (5):
>   Adding an S-expression structured output printer.
>   Rename the -json printer functions in notmuch-reply and notmuch-show
>     to     generic -sprinter functions.
>   Use the S-Expression structured printer in notmuch-show,
>     notmuch-reply     and notmuch-search.
>   Adding tests for --format=sexp.
>   Updating man pages for new S-Expression output format.
>
>  Makefile.local            |   1 +
>  devel/schemata            |   8 +-
>  man/man1/notmuch-reply.1  |  14 ++-
>  man/man1/notmuch-search.1 |  15 +--
>  man/man1/notmuch-show.1   |  36 +++++--
>  notmuch-client.h          |   8 +-
>  notmuch-reply.c           |  48 ++++++----
>  notmuch-search.c          |   6 +-
>  notmuch-show.c            |  65 +++++++------
>  sprinter-sexp.c           | 238 ++++++++++++++++++++++++++++++++++++++++++++++
>  sprinter.h                |   4 +
>  test/notmuch-test         |   1 +
>  test/sexp                 |  48 ++++++++++
>  13 files changed, 423 insertions(+), 69 deletions(-)
>  create mode 100644 sprinter-sexp.c
>  create mode 100755 test/sexp
>
> -- 
> 1.8.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

[-- Attachment #2: Type: application/pgp-signature, Size: 835 bytes --]

^ permalink raw reply	[flat|nested] 38+ messages in thread

end of thread, other threads:[~2012-12-07  2:54 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-11-30  8:29 [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
2012-11-30  8:29 ` [PATCH 2/3] Use the S-Expression structured printer for notmuch search Peter Feigl
2012-11-30 18:04   ` Jani Nikula
2012-11-30  8:29 ` [PATCH 3/3] Use the S-Expression structured printer in notmuch show and notmuch reply Peter Feigl
2012-11-30 19:06   ` Jani Nikula
2012-11-30  8:34 ` [PATCH 1/3] Adding an S-expression structured output printer Peter Feigl
2012-11-30 17:26 ` Jani Nikula
2012-11-30 19:11 ` Jani Nikula
2012-12-01  9:59 ` Mark Walters
2012-12-01 11:34   ` Tomi Ollila
2012-12-01 12:24     ` Mark Walters
2012-12-01 13:29       ` Tomi Ollila
2012-12-01 13:45         ` Mark Walters
2012-12-01 14:26           ` Mark Walters
2012-12-01 15:46             ` Mark Walters
2012-12-01 15:18       ` Austin Clements
2012-12-01 16:14 ` Mark Walters
2012-12-04 14:46 ` [PATCH v2 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
2012-12-04 17:07   ` Jani Nikula
2012-12-06  7:33   ` [PATCH v3 " Peter Feigl
2012-12-06  8:22     ` Mark Walters
2012-12-07  2:54     ` Jameson Graef Rollins
2012-12-06  7:33   ` [PATCH v3 1/5] Adding an S-expression structured output printer Peter Feigl
2012-12-06  7:33   ` [PATCH v3 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
2012-12-06  7:33   ` [PATCH v3 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
2012-12-06  7:33   ` [PATCH v3 4/5] Adding tests for --format=sexp Peter Feigl
2012-12-06  8:18     ` Mark Walters
2012-12-06  7:33   ` [PATCH v3 5/5] Updating man pages for new S-Expression output format Peter Feigl
2012-12-06  8:19     ` Mark Walters
2012-12-04 14:46 ` [PATCH v2 1/5] Adding an S-expression structured output printer Peter Feigl
2012-12-04 19:14   ` Austin Clements
2012-12-04 14:46 ` [PATCH v2 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
2012-12-04 19:18   ` Austin Clements
2012-12-04 14:46 ` [PATCH v2 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
2012-12-04 19:22   ` Austin Clements
2012-12-04 14:46 ` [PATCH v2 4/5] Adding tests for --format=sexp Peter Feigl
2012-12-04 19:24   ` Austin Clements
2012-12-04 14:46 ` [PATCH v2 5/5] Updating man pages for new S-Expression output format Peter Feigl

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).