From: Peter Feigl <craven@gmx.net>
To: notmuch@notmuchmail.org
Subject: [PATCH] rewriting notmuch-search for structured output to make other output formats easier
Date: Sat, 21 Jan 2012 22:16:08 +0100 [thread overview]
Message-ID: <1327180568-30385-1-git-send-email-craven@gmx.net> (raw)
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
next reply other threads:[~2012-01-21 21:23 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-01-21 21:16 Peter Feigl [this message]
2012-01-21 22:04 ` [PATCH] rewriting notmuch-search for structured output to make other output formats easier Austin Clements
2012-01-21 23:17 ` Peter Feigl
2012-01-22 0:23 ` Austin Clements
2012-01-21 23:12 ` Jameson Graef Rollins
2012-01-21 23:21 ` Peter Feigl
2012-01-22 0:34 ` Jameson Graef Rollins
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://notmuchmail.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1327180568-30385-1-git-send-email-craven@gmx.net \
--to=craven@gmx.net \
--cc=notmuch@notmuchmail.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).