unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH v4 0/5] New output format sexp (Lisp S-Expressions)
@ 2012-12-06 11:44 Peter Feigl
  2012-12-06 11:44 ` [PATCH v4 1/5] Adding an S-expression structured output printer Peter Feigl
                   ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Peter Feigl @ 2012-12-06 11:44 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, Emacs 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 adresses comments by Mark Walters.

Old versions:
v3: id:1354779189-12231-1-git-send-email-craven@gmx.net
v2: id:1354632382-15609-1-git-send-email-craven@gmx.net
v1: 1354264143-30173-1-git-send-email-craven@gmx.net

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] 14+ messages in thread

* [PATCH v4 1/5] Adding an S-expression structured output printer.
  2012-12-06 11:44 [PATCH v4 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
@ 2012-12-06 11:44 ` Peter Feigl
  2012-12-06 15:02   ` Tomi Ollila
  2012-12-06 15:44   ` Austin Clements
  2012-12-06 11:44 ` [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 14+ messages in thread
From: Peter Feigl @ 2012-12-06 11:44 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] 14+ messages in thread

* [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions.
  2012-12-06 11:44 [PATCH v4 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
  2012-12-06 11:44 ` [PATCH v4 1/5] Adding an S-expression structured output printer Peter Feigl
@ 2012-12-06 11:44 ` Peter Feigl
  2012-12-06 15:08   ` Tomi Ollila
  2012-12-06 15:48   ` Austin Clements
  2012-12-06 11:44 ` [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 14+ messages in thread
From: Peter Feigl @ 2012-12-06 11:44 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] 14+ messages in thread

* [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-06 11:44 [PATCH v4 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
  2012-12-06 11:44 ` [PATCH v4 1/5] Adding an S-expression structured output printer Peter Feigl
  2012-12-06 11:44 ` [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
@ 2012-12-06 11:44 ` Peter Feigl
  2012-12-06 15:17   ` Tomi Ollila
  2012-12-07  2:58   ` Jameson Graef Rollins
  2012-12-06 11:44 ` [PATCH v4 4/5] Adding tests for --format=sexp Peter Feigl
  2012-12-06 11:44 ` [PATCH v4 5/5] Updating man pages for new S-Expression output format Peter Feigl
  4 siblings, 2 replies; 14+ messages in thread
From: Peter Feigl @ 2012-12-06 11:44 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] 14+ messages in thread

* [PATCH v4 4/5] Adding tests for --format=sexp.
  2012-12-06 11:44 [PATCH v4 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                   ` (2 preceding siblings ...)
  2012-12-06 11:44 ` [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
@ 2012-12-06 11:44 ` Peter Feigl
  2012-12-06 11:44 ` [PATCH v4 5/5] Updating man pages for new S-Expression output format Peter Feigl
  4 siblings, 0 replies; 14+ messages in thread
From: Peter Feigl @ 2012-12-06 11:44 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..b804942
--- /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 \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :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 \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :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 \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :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 \"${gen_msg_id}\" :match t :excluded nil :filename \"${gen_msg_filename}\" :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 \"$id\" :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] 14+ messages in thread

* [PATCH v4 5/5] Updating man pages for new S-Expression output format.
  2012-12-06 11:44 [PATCH v4 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
                   ` (3 preceding siblings ...)
  2012-12-06 11:44 ` [PATCH v4 4/5] Adding tests for --format=sexp Peter Feigl
@ 2012-12-06 11:44 ` Peter Feigl
  4 siblings, 0 replies; 14+ messages in thread
From: Peter Feigl @ 2012-12-06 11:44 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..fa04c9e 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
+formats do 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] 14+ messages in thread

* Re: [PATCH v4 1/5] Adding an S-expression structured output printer.
  2012-12-06 11:44 ` [PATCH v4 1/5] Adding an S-expression structured output printer Peter Feigl
@ 2012-12-06 15:02   ` Tomi Ollila
  2012-12-06 15:44   ` Austin Clements
  1 sibling, 0 replies; 14+ messages in thread
From: Tomi Ollila @ 2012-12-06 15:02 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Thu, Dec 06 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 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);

The code that compiler cares looks ok to me, so I'm commenting these:
spaces after for & if (and strchr) above.


> +	}
> +    }
> +    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
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

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

* Re: [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions.
  2012-12-06 11:44 ` [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
@ 2012-12-06 15:08   ` Tomi Ollila
  2012-12-06 15:48   ` Austin Clements
  1 sibling, 0 replies; 14+ messages in thread
From: Tomi Ollila @ 2012-12-06 15:08 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Thu, Dec 06 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  | 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,

There should be spave between ) & ( but the original did not have that;
alsu uncrustify (>= 0.53) cannot add those :(
.. hmm, so, ignore for the time beign ;)

> +			     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);

In many of these s/_json_/_sprinter_/ changes the following line indents
are missing. It sure makes the diff smaller but... ;)

>  
>  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)

ditto :) (skipping rest dittos ;)

>  {
>      /* 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] 14+ messages in thread

* Re: [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-06 11:44 ` [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
@ 2012-12-06 15:17   ` Tomi Ollila
  2012-12-07  2:58   ` Jameson Graef Rollins
  1 sibling, 0 replies; 14+ messages in thread
From: Tomi Ollila @ 2012-12-06 15:17 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Thu, Dec 06 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.
> ---
>  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. */

... and the last formatting comment: re-fill comment so that it fits
into 80 columns (as new patch series is needed to fix the cardinal
errors w/ 'for(' & 'if(' before :) (not counting the findings others might
find). 

>      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
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

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

* Re: [PATCH v4 1/5] Adding an S-expression structured output printer.
  2012-12-06 11:44 ` [PATCH v4 1/5] Adding an S-expression structured output printer Peter Feigl
  2012-12-06 15:02   ` Tomi Ollila
@ 2012-12-06 15:44   ` Austin Clements
  1 sibling, 0 replies; 14+ messages in thread
From: Austin Clements @ 2012-12-06 15:44 UTC (permalink / raw)
  To: Peter Feigl, notmuch

On Thu, 06 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 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);

Should be "\\%03o" (one less o).

> +    }
> +    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'

There are a large number of illegal symbol characters.  I would go the
conservative route here (since we get to choose our symbols) and only
allow
  isalnum(x) || (x == '-') || (x == '_')
(I would prefer not to allow _ but it's already in the schema.)

> +    };
> +    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);
> +}

How about
    struct sprinter_sexp *sps = sexp_begin_value (sp);

    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
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

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

* Re: [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions.
  2012-12-06 11:44 ` [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
  2012-12-06 15:08   ` Tomi Ollila
@ 2012-12-06 15:48   ` Austin Clements
  1 sibling, 0 replies; 14+ messages in thread
From: Austin Clements @ 2012-12-06 15:48 UTC (permalink / raw)
  To: Peter Feigl, notmuch

Just a nit: It would be nice to also replace _json with _sprinter in
devel/schemata (those cross-references are always out of date anyway,
but might as well try).

On Thu, 06 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  | 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
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

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

* Re: [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-06 11:44 ` [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
  2012-12-06 15:17   ` Tomi Ollila
@ 2012-12-07  2:58   ` Jameson Graef Rollins
  2012-12-07  8:01     ` Mark Walters
  1 sibling, 1 reply; 14+ messages in thread
From: Jameson Graef Rollins @ 2012-12-07  2:58 UTC (permalink / raw)
  To: Peter Feigl, notmuch

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

On Thu, Dec 06 2012, Peter Feigl <craven@gmx.net> wrote:
> -    /* 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. */

Can we just make these comments more general, so we don't have to tweak
them every time a new formatter is added?

jamie.

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

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

* Re: [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-07  2:58   ` Jameson Graef Rollins
@ 2012-12-07  8:01     ` Mark Walters
  2012-12-07  8:24       ` Jameson Graef Rollins
  0 siblings, 1 reply; 14+ messages in thread
From: Mark Walters @ 2012-12-07  8:01 UTC (permalink / raw)
  To: Jameson Graef Rollins, Peter Feigl, notmuch


Jameson Graef Rollins <jrollins@finestructure.net> writes:

> On Thu, Dec 06 2012, Peter Feigl <craven@gmx.net> wrote:
>> -    /* 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. */
>
> Can we just make these comments more general, so we don't have to tweak
> them every time a new formatter is added?

I think I agree, but I think it may as well wait until we have more than
the two formats. In other words I would suggest not holding up the
(latest version) of this series for this.

Best wishes

Mark

>
> jamie.
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

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

* Re: [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search.
  2012-12-07  8:01     ` Mark Walters
@ 2012-12-07  8:24       ` Jameson Graef Rollins
  0 siblings, 0 replies; 14+ messages in thread
From: Jameson Graef Rollins @ 2012-12-07  8:24 UTC (permalink / raw)
  To: Mark Walters, Peter Feigl, notmuch

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

On Fri, Dec 07 2012, Mark Walters <markwalters1009@gmail.com> wrote:
> Jameson Graef Rollins <jrollins@finestructure.net> writes:
>
>> On Thu, Dec 06 2012, Peter Feigl <craven@gmx.net> wrote:
>>> -    /* 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. */
>>
>> Can we just make these comments more general, so we don't have to tweak
>> them every time a new formatter is added?
>
> I think I agree, but I think it may as well wait until we have more than
> the two formats. In other words I would suggest not holding up the
> (latest version) of this series for this.

Yes, that's totally fair.  I didn't notice later that a new version of
the patch had already been sent.

I haven't had a chance to review the patches, but I am certainly all for
the feature.  Definitely don't hold it up on this account.

jamie.

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

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

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

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-12-06 11:44 [PATCH v4 0/5] New output format sexp (Lisp S-Expressions) Peter Feigl
2012-12-06 11:44 ` [PATCH v4 1/5] Adding an S-expression structured output printer Peter Feigl
2012-12-06 15:02   ` Tomi Ollila
2012-12-06 15:44   ` Austin Clements
2012-12-06 11:44 ` [PATCH v4 2/5] Rename the -json printer functions in notmuch-reply and notmuch-show to generic -sprinter functions Peter Feigl
2012-12-06 15:08   ` Tomi Ollila
2012-12-06 15:48   ` Austin Clements
2012-12-06 11:44 ` [PATCH v4 3/5] Use the S-Expression structured printer in notmuch-show, notmuch-reply and notmuch-search Peter Feigl
2012-12-06 15:17   ` Tomi Ollila
2012-12-07  2:58   ` Jameson Graef Rollins
2012-12-07  8:01     ` Mark Walters
2012-12-07  8:24       ` Jameson Graef Rollins
2012-12-06 11:44 ` [PATCH v4 4/5] Adding tests for --format=sexp Peter Feigl
2012-12-06 11:44 ` [PATCH v4 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).