[PATCH] rewriting notmuch-search for structured output to make other output formats easier

Peter Feigl craven at gmx.net
Sat Jan 21 13:16:08 PST 2012


The output routines have been rewritten so that logical structure
(objects with key/value pairs, arrays, strings and numbers) are
written instead of ad-hoc printfs. This allows for easier adaptation
of other output formats, as only the routines that start/end an object
etc. have to be rewritten. The logic is the same for all formats.
The default text output is handled differently, special cases are
inserted at the proper places, as it differs too much from the
structured output.
---
 notmuch-search.c |  493 ++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 309 insertions(+), 184 deletions(-)

diff --git a/notmuch-search.c b/notmuch-search.c
index 8867aab..bce44c2 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -29,88 +29,221 @@ typedef enum {
 } output_t;
 
 typedef struct search_format {
-    const char *results_start;
-    const char *item_start;
-    void (*item_id) (const void *ctx,
-		     const char *item_type,
-		     const char *item_id);
-    void (*thread_summary) (const void *ctx,
-			    const char *thread_id,
-			    const time_t date,
-			    const int matched,
-			    const int total,
-			    const char *authors,
-			    const char *subject);
-    const char *tag_start;
-    const char *tag;
-    const char *tag_sep;
-    const char *tag_end;
-    const char *item_sep;
-    const char *item_end;
-    const char *results_end;
-    const char *results_null;
+    void (*start_object) (const void *ctx, FILE *stream);
+    void (*end_object) (const void *ctx, FILE *stream);
+    void (*start_attribute) (const void *ctx, FILE *stream);
+    void (*attribute_key) (const void *ctx, FILE *stream, const char *key);
+    void (*attribute_key_value_separator) (const void *ctx, FILE *stream);
+    void (*end_attribute) (const void *ctx, FILE *stream);
+    void (*attribute_separator) (const void *ctx, FILE *stream);
+    void (*start_array) (const void *ctx, FILE *stream);
+    void (*array_item_separator) (const void *ctx, FILE *stream);
+    void (*end_array) (const void *ctx, FILE *stream);
+    void (*number) (const void *ctx, FILE *stream, int number);
+    void (*string) (const void *ctx, FILE *stream, const char *string);
+    void (*boolean) (const void *ctx, FILE *stream, int boolean);
 } search_format_t;
 
-static void
-format_item_id_text (const void *ctx,
-		     const char *item_type,
-		     const char *item_id);
-
-static void
-format_thread_text (const void *ctx,
-		    const char *thread_id,
-		    const time_t date,
-		    const int matched,
-		    const int total,
-		    const char *authors,
-		    const char *subject);
+/* dummy format */
 static const search_format_t format_text = {
-    "",
-	"",
-	    format_item_id_text,
-	    format_thread_text,
-	    " (",
-		"%s", " ",
-	    ")", "\n",
-	"",
-    "\n",
-    "",
-};
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+void json_start_object(const void *ctx, FILE *stream);
+void json_end_object(const void *ctx, FILE *stream);
+void json_start_attribute(const void *ctx, FILE *stream);
+void json_attribute_key(const void *ctx, FILE *stream, const char *key);
+void json_attribute_key_value_separator(const void *ctx, FILE *stream);
+void json_end_attribute(const void *ctx, FILE *stream);
+void json_attribute_separator(const void *ctx, FILE *stream);
+void json_start_array(const void *ctx, FILE *stream);
+void json_array_item_separator(const void *ctx, FILE *stream);
+void json_end_array(const void *ctx, FILE *stream);
+void json_number(const void *ctx, FILE *stream, int number);
+void json_string(const void *ctx, FILE *stream, const char *string);
+void json_boolean(const void *ctx, FILE *stream, int boolean);
+
+
+void json_start_object(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("{", stream);
+}
+void json_end_object(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("}\n", stream);
+}
+void json_start_attribute(const void *ctx, FILE *stream) {
+    (void)ctx;
+    (void)stream;
+}
+void json_attribute_key(const void *ctx, FILE *stream, const char *key) {
+    (void)ctx;
+    fprintf(stream, "\"%s\"", key);
+}
+void json_attribute_key_value_separator(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(": ", stream);
+}
+void json_end_attribute(const void *ctx, FILE *stream) {
+    (void)ctx;
+    (void)stream;
+}
+void json_attribute_separator(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(",\n", stream);
+}
+void json_start_array(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("[", stream);
+}
+void json_array_item_separator(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(", ", stream);
+}
+void json_end_array(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("]", stream);
+}
+void json_number(const void *ctx, FILE *stream, int number) {
+    (void)ctx;
+    fprintf(stream, "%i", number);
+}
+void json_string(const void *ctx, FILE *stream, const char *string) {
+    void *ctx_quote = talloc_new (ctx);    
+    fprintf(stream, "%s", json_quote_str (ctx_quote, string));
+    talloc_free (ctx_quote);
+}
+void json_boolean(const void *ctx, FILE *stream, int boolean) {
+    (void)ctx;
+    if(boolean)
+	fputs("true", stream);
+    else
+	fputs("false", stream);
+}
+
+/* helper functions for attributes */
+void format_attribute_string(const void *ctx, FILE *stream, const search_format_t *format, const char *key, const char *value);
+void format_attribute_number(const void *ctx, FILE *stream, const search_format_t *format, const char *key, int value);
+void format_attribute_boolean(const void *ctx, FILE *stream, const search_format_t *format, const char *key, const char *value);
+
+void format_attribute_string(const void *ctx, FILE *stream, const search_format_t *format, const char *key, const char *value) {
+    format->start_attribute(ctx, stream);
+    format->attribute_key(ctx, stream, key);
+    format->attribute_key_value_separator(ctx, stream);
+    format->string(ctx, stream, value);
+    format->end_attribute(ctx, stream);
+}
+
+void format_attribute_number(const void *ctx, FILE *stream, const search_format_t *format, const char *key, int value) {
+    format->start_attribute(ctx, stream);
+    format->attribute_key(ctx, stream, key);
+    format->attribute_key_value_separator(ctx, stream);
+    format->number(ctx, stream, value);
+    format->end_attribute(ctx, stream);
+}
 
-static void
-format_item_id_json (const void *ctx,
-		     const char *item_type,
-		     const char *item_id);
-
-static void
-format_thread_json (const void *ctx,
-		    const char *thread_id,
-		    const time_t date,
-		    const int matched,
-		    const int total,
-		    const char *authors,
-		    const char *subject);
 static const search_format_t format_json = {
-    "[",
-	"{",
-	    format_item_id_json,
-	    format_thread_json,
-	    "\"tags\": [",
-		"\"%s\"", ", ",
-	    "]", ",\n",
-	"}",
-    "]\n",
-    "]\n",
+    json_start_object,
+    json_end_object,
+    json_start_attribute,
+    json_attribute_key,
+    json_attribute_key_value_separator,
+    json_end_attribute,
+    json_attribute_separator,
+    json_start_array,
+    json_array_item_separator,
+    json_end_array,
+    json_number,
+    json_string,
+    json_boolean,
 };
 
-static void
-format_item_id_text (unused (const void *ctx),
-		     const char *item_type,
-		     const char *item_id)
-{
-    printf ("%s%s", item_type, item_id);
+void sexp_start_object(const void *ctx, FILE *stream);
+void sexp_end_object(const void *ctx, FILE *stream);
+void sexp_start_attribute(const void *ctx, FILE *stream);
+void sexp_attribute_key(const void *ctx, FILE *stream, const char *key);
+void sexp_attribute_key_value_separator(const void *ctx, FILE *stream);
+void sexp_end_attribute(const void *ctx, FILE *stream);
+void sexp_attribute_separator(const void *ctx, FILE *stream);
+void sexp_start_array(const void *ctx, FILE *stream);
+void sexp_array_item_separator(const void *ctx, FILE *stream);
+void sexp_end_array(const void *ctx, FILE *stream);
+void sexp_number(const void *ctx, FILE *stream, int number);
+void sexp_string(const void *ctx, FILE *stream, const char *string);
+void sexp_boolean(const void *ctx, FILE *stream, int boolean);
+
+void sexp_start_object(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("(", stream);
+}
+void sexp_end_object(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(")\n", stream);
+}
+void sexp_start_attribute(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("(", stream);
+}
+void sexp_attribute_key(const void *ctx, FILE *stream, const char *key) {
+    (void)ctx;
+    fprintf(stream, "%s", key);
+}
+void sexp_attribute_key_value_separator(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(" ", stream);
+}
+void sexp_end_attribute(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(")\n", stream);
+}
+void sexp_attribute_separator(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(" ", stream);
+}
+void sexp_start_array(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs("(", stream);
+}
+void sexp_array_item_separator(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(" ", stream);
+}
+void sexp_end_array(const void *ctx, FILE *stream) {
+    (void)ctx;
+    fputs(")", stream);
+}
+void sexp_number(const void *ctx, FILE *stream, int number) {
+    (void)ctx;
+    fprintf(stream, "%i", number);
+}
+void sexp_string(const void *ctx, FILE *stream, const char *string) {
+    void *ctx_quote = talloc_new (ctx);    
+    fprintf(stream, "%s", json_quote_str (ctx_quote, string));
+    talloc_free (ctx_quote);
+}
+void sexp_boolean(const void *ctx, FILE *stream, int boolean) {
+    (void)ctx;
+    if(boolean)
+	fputs("#t", stream);
+    else
+	fputs("#f", stream);
 }
 
+static const search_format_t format_sexp = {
+    sexp_start_object,
+    sexp_end_object,
+    sexp_start_attribute,
+    sexp_attribute_key,
+    sexp_attribute_key_value_separator,
+    sexp_end_attribute,
+    sexp_attribute_separator,
+    sexp_start_array,
+    sexp_array_item_separator,
+    sexp_end_array,
+    sexp_number,
+    sexp_string,
+    sexp_boolean,
+};
+
 static char *
 sanitize_string (const void *ctx, const char *str)
 {
@@ -128,70 +261,6 @@ sanitize_string (const void *ctx, const char *str)
     return out;
 }
 
-static void
-format_thread_text (const void *ctx,
-		    const char *thread_id,
-		    const time_t date,
-		    const int matched,
-		    const int total,
-		    const char *authors,
-		    const char *subject)
-{
-    void *ctx_quote = talloc_new (ctx);
-
-    printf ("thread:%s %12s [%d/%d] %s; %s",
-	    thread_id,
-	    notmuch_time_relative_date (ctx, date),
-	    matched,
-	    total,
-	    sanitize_string (ctx_quote, authors),
-	    sanitize_string (ctx_quote, subject));
-
-    talloc_free (ctx_quote);
-}
-
-static void
-format_item_id_json (const void *ctx,
-		     unused (const char *item_type),
-		     const char *item_id)
-{
-    void *ctx_quote = talloc_new (ctx);
-
-    printf ("%s", json_quote_str (ctx_quote, item_id));
-
-    talloc_free (ctx_quote);
-    
-}
-
-static void
-format_thread_json (const void *ctx,
-		    const char *thread_id,
-		    const time_t date,
-		    const int matched,
-		    const int total,
-		    const char *authors,
-		    const char *subject)
-{
-    void *ctx_quote = talloc_new (ctx);
-
-    printf ("\"thread\": %s,\n"
-	    "\"timestamp\": %ld,\n"
-	    "\"date_relative\": \"%s\",\n"
-	    "\"matched\": %d,\n"
-	    "\"total\": %d,\n"
-	    "\"authors\": %s,\n"
-	    "\"subject\": %s,\n",
-	    json_quote_str (ctx_quote, thread_id),
-	    date,
-	    notmuch_time_relative_date (ctx, date),
-	    matched,
-	    total,
-	    json_quote_str (ctx_quote, authors),
-	    json_quote_str (ctx_quote, subject));
-
-    talloc_free (ctx_quote);
-}
-
 static int
 do_search_threads (const search_format_t *format,
 		   notmuch_query_t *query,
@@ -217,7 +286,8 @@ do_search_threads (const search_format_t *format,
     if (threads == NULL)
 	return 1;
 
-    fputs (format->results_start, stdout);
+    if(format != &format_text)
+	format->start_array(threads, stdout);
 
     for (i = 0;
 	 notmuch_threads_valid (threads) && (limit < 0 || i < offset + limit);
@@ -232,43 +302,82 @@ do_search_threads (const search_format_t *format,
 	    continue;
 	}
 
-	if (! first_thread)
-	    fputs (format->item_sep, stdout);
+	if (! first_thread && format != &format_text)
+	    format->array_item_separator(thread, stdout);
 
 	if (output == OUTPUT_THREADS) {
-	    format->item_id (thread, "thread:",
-			     notmuch_thread_get_thread_id (thread));
+	    const char *thread_id = notmuch_thread_get_thread_id (thread);
+	    if(format != &format_text)
+		//format_attribute_string(thread, stdout, format, "thread", thread_id);
+		format->string(thread, stdout, thread_id);
+	    else /* text format */
+		printf("thread:%s\n", notmuch_thread_get_thread_id (thread));
 	} else { /* output == OUTPUT_SUMMARY */
-	    fputs (format->item_start, stdout);
+	    if(format != &format_text)
+		format->start_object(thread, stdout);
 
 	    if (sort == NOTMUCH_SORT_OLDEST_FIRST)
 		date = notmuch_thread_get_oldest_date (thread);
 	    else
 		date = notmuch_thread_get_newest_date (thread);
 
-	    format->thread_summary (thread,
-				    notmuch_thread_get_thread_id (thread),
-				    date,
-				    notmuch_thread_get_matched_messages (thread),
-				    notmuch_thread_get_total_messages (thread),
-				    notmuch_thread_get_authors (thread),
-				    notmuch_thread_get_subject (thread));
-
-	    fputs (format->tag_start, stdout);
+	    if(format != &format_text) {
+		format_attribute_string(thread, stdout, format, "thread", notmuch_thread_get_thread_id (thread));
+		format->attribute_separator(thread, stdout);
+		format_attribute_number(thread, stdout, format, "timestamp", date);
+		format->attribute_separator(thread, stdout);
+/*		format_attribute_string(thread, stdout, format, "date_relative", notmuch_time_relative_date (thread, date)); */
+/*		format->attribute_separator(thread, stdout); */
+		format_attribute_number(thread, stdout, format, "matched", notmuch_thread_get_matched_messages (thread));
+		format->attribute_separator(thread, stdout);
+		format_attribute_number(thread, stdout, format, "total", notmuch_thread_get_total_messages (thread));
+		format->attribute_separator(thread, stdout);
+		format_attribute_string(thread, stdout, format, "authors", notmuch_thread_get_authors (thread));
+		format->attribute_separator(thread, stdout);
+		format_attribute_string(thread, stdout, format, "subject", notmuch_thread_get_subject (thread));
+		format->attribute_separator(thread, stdout);
+
+		format->start_attribute(thread, stdout);
+		format->attribute_key(thread, stdout, "tags");
+		format->attribute_key_value_separator(thread, stdout);
+		format->start_array(thread, stdout);
+	    } else { /* text format */
+		void *ctx_quote = talloc_new (thread);
+		printf ("thread:%s %12s [%d/%d] %s; %s (",
+			notmuch_thread_get_thread_id (thread),
+			notmuch_time_relative_date (ctx_quote, date),
+			notmuch_thread_get_matched_messages (thread),
+			notmuch_thread_get_total_messages (thread),
+			sanitize_string (ctx_quote, notmuch_thread_get_authors (thread)),
+			sanitize_string (ctx_quote, notmuch_thread_get_subject (thread)));
+		talloc_free (ctx_quote);
+	    }
 
 	    for (tags = notmuch_thread_get_tags (thread);
 		 notmuch_tags_valid (tags);
 		 notmuch_tags_move_to_next (tags))
 	    {
-		if (! first_tag)
-		    fputs (format->tag_sep, stdout);
-		printf (format->tag, notmuch_tags_get (tags));
+		if (! first_tag) {
+		    if(format != &format_text)
+			format->array_item_separator(thread, stdout);
+		    else /* text format */
+			printf(" ");
+		}
+		if(format != &format_text)
+		    format->string(thread, stdout, notmuch_tags_get(tags));
+		else /* text format */
+		    printf("%s", notmuch_tags_get(tags));
 		first_tag = 0;
 	    }
 
-	    fputs (format->tag_end, stdout);
+	    if(format != &format_text) {
+		format->end_array(thread, stdout);
+		format->end_attribute(thread, stdout);
+		format->end_object(thread, stdout);
+	    } else {
+		printf(")\n");
+	    }
 
-	    fputs (format->item_end, stdout);
 	}
 
 	first_thread = 0;
@@ -276,10 +385,10 @@ do_search_threads (const search_format_t *format,
 	notmuch_thread_destroy (thread);
     }
 
-    if (first_thread)
-	fputs (format->results_null, stdout);
-    else
-	fputs (format->results_end, stdout);
+    if(format != &format_text) {
+	format->end_array(threads, stdout);
+	fputs("\n", stdout);
+    }
 
     return 0;
 }
@@ -307,7 +416,8 @@ do_search_messages (const search_format_t *format,
     if (messages == NULL)
 	return 1;
 
-    fputs (format->results_start, stdout);
+    if(format != &format_text)
+	format->start_array(messages, stdout);
 
     for (i = 0;
 	 notmuch_messages_valid (messages) && (limit < 0 || i < offset + limit);
@@ -325,11 +435,15 @@ do_search_messages (const search_format_t *format,
 		 notmuch_filenames_valid (filenames);
 		 notmuch_filenames_move_to_next (filenames))
 	    {
-		if (! first_message)
-		    fputs (format->item_sep, stdout);
+		if (! first_message) {
+		    if(format != &format_text)
+			format->array_item_separator(message, stdout);
+		}
 
-		format->item_id (message, "",
-				 notmuch_filenames_get (filenames));
+		if(format != &format_text)
+		    format->string(message, stdout, notmuch_filenames_get (filenames));
+		else
+		    printf("%s\n", notmuch_filenames_get (filenames));
 
 		first_message = 0;
 	    }
@@ -337,11 +451,15 @@ do_search_messages (const search_format_t *format,
 	    notmuch_filenames_destroy( filenames );
 
 	} else { /* output == OUTPUT_MESSAGES */
-	    if (! first_message)
-		fputs (format->item_sep, stdout);
+	    if (! first_message) {
+	    if(format != &format_text)
+		format->array_item_separator(message, stdout);
+	    }
 
-	    format->item_id (message, "id:",
-			     notmuch_message_get_message_id (message));
+	    if(format != &format_text)
+		format->string(message, stdout, notmuch_message_get_message_id (message));
+	    else /* text format */
+		printf("id:%s\n", notmuch_message_get_message_id (message));
 	    first_message = 0;
 	}
 
@@ -350,11 +468,11 @@ do_search_messages (const search_format_t *format,
 
     notmuch_messages_destroy (messages);
 
-    if (first_message)
-	fputs (format->results_null, stdout);
-    else
-	fputs (format->results_end, stdout);
-
+    if(format != &format_text) {
+	format->end_array(messages, stdout);
+	fputs("\n", stdout);
+    }
+    
     return 0;
 }
 
@@ -381,7 +499,8 @@ do_search_tags (notmuch_database_t *notmuch,
     if (tags == NULL)
 	return 1;
 
-    fputs (format->results_start, stdout);
+    if(format != &format_text)
+	format->start_array(tags, stdout);
 
     for (;
 	 notmuch_tags_valid (tags);
@@ -389,10 +508,15 @@ do_search_tags (notmuch_database_t *notmuch,
     {
 	tag = notmuch_tags_get (tags);
 
-	if (! first_tag)
-	    fputs (format->item_sep, stdout);
+	if (! first_tag) {
+	    if(format != &format_text)
+		format->array_item_separator(tag, stdout);
+	}
 
-	format->item_id (tags, "", tag);
+	if(format != &format_text)
+	    format->string(tags, stdout, tag);
+	else
+	    printf("%s\n", tag);
 
 	first_tag = 0;
     }
@@ -402,10 +526,8 @@ do_search_tags (notmuch_database_t *notmuch,
     if (messages)
 	notmuch_messages_destroy (messages);
 
-    if (first_tag)
-	fputs (format->results_null, stdout);
-    else
-	fputs (format->results_end, stdout);
+    if(format != &format_text)
+	format->end_array(tags, stdout);
 
     return 0;
 }
@@ -427,7 +549,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
     size_t auto_exclude_tags_length;
     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[] = {
@@ -437,6 +559,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',
@@ -464,6 +587,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
     case NOTMUCH_FORMAT_JSON:
 	format = &format_json;
 	break;
+    case NOTMUCH_FORMAT_SEXP:
+	format = &format_sexp;
     }
 
     config = notmuch_config_open (ctx, NULL, NULL);
-- 
1.7.7.3



More information about the notmuch mailing list