aboutsummaryrefslogtreecommitdiff
path: root/notmuch-show.c
diff options
context:
space:
mode:
authorScott Robinson <scott@quadhome.com>2009-12-31 11:17:40 -0400
committerCarl Worth <cworth@cworth.org>2010-02-23 12:01:12 -0800
commit6ce2bf68f53fec4302121763e588c33c7cd86a9c (patch)
tree97eed1a84956f582cc43efbb16419c43b930c242 /notmuch-show.c
parent3ca7a4fbcc9cd72330bce1b1670150af1e250006 (diff)
Add an "--format=(json|text)" command-line option to both notmuch-search and notmuch-show.
In the case of notmuch-show, "--format=json" also implies "--entire-thread" as the thread structure is implicit in the emitted document tree. As a coincidence to the implementation, multipart message ID numbers are now incremented with each part printed. This changes the previous semantics, which were unclear and not necessary related to the actual ordering of the message parts.
Diffstat (limited to 'notmuch-show.c')
-rw-r--r--notmuch-show.c278
1 files changed, 238 insertions, 40 deletions
diff --git a/notmuch-show.c b/notmuch-show.c
index 376aacd..1a1d601 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -20,8 +20,65 @@
#include "notmuch-client.h"
+typedef struct show_format {
+ const char *message_set_start;
+ const char *message_start;
+ void (*message) (const void *ctx,
+ notmuch_message_t *message,
+ int indent);
+ const char *header_start;
+ void (*header) (const void *ctx,
+ notmuch_message_t *message);
+ const char *header_end;
+ const char *body_start;
+ void (*part) (GMimeObject *part,
+ int *part_count);
+ const char *body_end;
+ const char *message_end;
+ const char *message_set_sep;
+ const char *message_set_end;
+} show_format_t;
+
+static void
+format_message_text (unused (const void *ctx),
+ notmuch_message_t *message,
+ int indent);
+static void
+format_headers_text (const void *ctx,
+ notmuch_message_t *message);
+static void
+format_part_text (GMimeObject *part,
+ int *part_count);
+static const show_format_t format_text = {
+ "",
+ "\fmessage{ ", format_message_text,
+ "\fheader{\n", format_headers_text, "\fheader}\n",
+ "\fbody{\n", format_part_text, "\fbody}\n",
+ "\fmessage}\n", "",
+ ""
+};
+
+static void
+format_message_json (const void *ctx,
+ notmuch_message_t *message,
+ unused (int indent));
+static void
+format_headers_json (const void *ctx,
+ notmuch_message_t *message);
+static void
+format_part_json (GMimeObject *part,
+ int *part_count);
+static const show_format_t format_json = {
+ "[",
+ "{", format_message_json,
+ ", \"headers\": {", format_headers_json, "}",
+ ", \"body\": [", format_part_json, "]",
+ "}", ", ",
+ "]"
+};
+
static const char *
-_get_tags_as_string (void *ctx, notmuch_message_t *message)
+_get_tags_as_string (const void *ctx, notmuch_message_t *message)
{
notmuch_tags_t *tags;
int first = 1;
@@ -48,7 +105,7 @@ _get_tags_as_string (void *ctx, notmuch_message_t *message)
/* Get a nice, single-line summary of message. */
static const char *
-_get_one_line_summary (void *ctx, notmuch_message_t *message)
+_get_one_line_summary (const void *ctx, notmuch_message_t *message)
{
const char *from;
time_t date;
@@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)
}
static void
-show_part_content (GMimeObject *part)
+format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)
+{
+ printf ("id:%s depth:%d match:%d filename:%s\n",
+ notmuch_message_get_message_id (message),
+ indent,
+ notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
+ notmuch_message_get_filename (message));
+}
+
+static void
+format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))
+{
+ void *ctx_quote = talloc_new (ctx);
+
+ printf ("\"id\": %s, \"match\": %s, \"filename\": %s",
+ json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
+ notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
+ json_quote_str (ctx_quote, notmuch_message_get_filename (message)));
+
+ talloc_free (ctx_quote);
+}
+
+static void
+format_headers_text (const void *ctx, notmuch_message_t *message)
+{
+ const char *headers[] = {
+ "Subject", "From", "To", "Cc", "Bcc", "Date"
+ };
+ const char *name, *value;
+ unsigned int i;
+
+ printf ("%s\n", _get_one_line_summary (ctx, message));
+
+ for (i = 0; i < ARRAY_SIZE (headers); i++) {
+ name = headers[i];
+ value = notmuch_message_get_header (message, name);
+ if (value)
+ printf ("%s: %s\n", name, value);
+ }
+}
+
+static void
+format_headers_json (const void *ctx, notmuch_message_t *message)
+{
+ const char *headers[] = {
+ "Subject", "From", "To", "Cc", "Bcc", "Date"
+ };
+ const char *name, *value;
+ unsigned int i;
+ int first_header = 1;
+ void *ctx_quote = talloc_new (ctx);
+
+ for (i = 0; i < ARRAY_SIZE (headers); i++) {
+ name = headers[i];
+ value = notmuch_message_get_header (message, name);
+ if (value)
+ {
+ if (!first_header)
+ fputs (", ", stdout);
+ first_header = 0;
+
+ printf ("%s: %s",
+ json_quote_str (ctx_quote, name),
+ json_quote_str (ctx_quote, value));
+ }
+ }
+
+ talloc_free (ctx_quote);
+}
+
+static void
+show_part_content (GMimeObject *part, GMimeStream *stream_out)
{
- GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
GMimeStream *stream_filter = NULL;
GMimeDataWrapper *wrapper;
const char *charset;
charset = g_mime_object_get_content_type_parameter (part, "charset");
- if (stream_stdout) {
- g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
- stream_filter = g_mime_stream_filter_new(stream_stdout);
+ if (stream_out) {
+ stream_filter = g_mime_stream_filter_new(stream_out);
g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
g_mime_filter_crlf_new(FALSE, FALSE));
if (charset) {
@@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)
g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
if (stream_filter)
g_object_unref(stream_filter);
- if (stream_stdout)
- g_object_unref(stream_stdout);
}
static void
-show_part (GMimeObject *part, int *part_count)
+format_part_text (GMimeObject *part, int *part_count)
{
GMimeContentDisposition *disposition;
GMimeContentType *content_type;
+ GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
+
+ g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
disposition = g_mime_object_get_content_disposition (part);
if (disposition &&
@@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)
if (g_mime_content_type_is_type (content_type, "text", "*") &&
!g_mime_content_type_is_type (content_type, "text", "html"))
{
- show_part_content (part);
+ show_part_content (part, stream_stdout);
}
printf ("\fattachment}\n");
+ if (stream_stdout)
+ g_object_unref(stream_stdout);
+
return;
}
@@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)
if (g_mime_content_type_is_type (content_type, "text", "*") &&
!g_mime_content_type_is_type (content_type, "text", "html"))
{
- show_part_content (part);
+ show_part_content (part, stream_stdout);
}
else
{
@@ -144,57 +274,96 @@ show_part (GMimeObject *part, int *part_count)
}
printf ("\fpart}\n");
+
+ if (stream_stdout)
+ g_object_unref(stream_stdout);
}
static void
-show_message (void *ctx, notmuch_message_t *message, int indent)
+format_part_json (GMimeObject *part, int *part_count)
{
- const char *headers[] = {
- "Subject", "From", "To", "Cc", "Bcc", "Date"
- };
- const char *name, *value;
- unsigned int i;
+ GMimeContentType *content_type;
+ GMimeContentDisposition *disposition;
+ void *ctx = talloc_new (NULL);
+ GMimeStream *stream_memory = g_mime_stream_mem_new ();
+ GByteArray *part_content;
- printf ("\fmessage{ id:%s depth:%d match:%d filename:%s\n",
- notmuch_message_get_message_id (message),
- indent,
- notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
- notmuch_message_get_filename (message));
+ content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
- printf ("\fheader{\n");
+ if (*part_count > 1)
+ fputs (", ", stdout);
- printf ("%s\n", _get_one_line_summary (ctx, message));
+ printf ("{\"id\": %d, \"content-type\": %s",
+ *part_count,
+ json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
- for (i = 0; i < ARRAY_SIZE (headers); i++) {
- name = headers[i];
- value = notmuch_message_get_header (message, name);
- if (value)
- printf ("%s: %s\n", name, value);
+ disposition = g_mime_object_get_content_disposition (part);
+ if (disposition &&
+ strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+ {
+ const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+
+ printf (", \"filename\": %s", json_quote_str (ctx, filename));
}
- printf ("\fheader}\n");
- printf ("\fbody{\n");
+ if (g_mime_content_type_is_type (content_type, "text", "*") &&
+ !g_mime_content_type_is_type (content_type, "text", "html"))
+ {
+ show_part_content (part, stream_memory);
+ part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
+
+ printf (", \"content\": %s", json_quote_str (ctx, (char *) part_content->data));
+ }
+
+ fputs ("}", stdout);
+
+ talloc_free (ctx);
+ if (stream_memory)
+ g_object_unref (stream_memory);
+}
+
+static void
+show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)
+{
+ fputs (format->message_start, stdout);
+ if (format->message)
+ format->message(ctx, message, indent);
- show_message_body (notmuch_message_get_filename (message), show_part);
+ fputs (format->header_start, stdout);
+ if (format->header)
+ format->header(ctx, message);
+ fputs (format->header_end, stdout);
- printf ("\fbody}\n");
+ fputs (format->body_start, stdout);
+ if (format->part)
+ show_message_body (notmuch_message_get_filename (message), format->part);
+ fputs (format->body_end, stdout);
- printf ("\fmessage}\n");
+ fputs (format->message_end, stdout);
}
static void
-show_messages (void *ctx, notmuch_messages_t *messages, int indent,
+show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,
notmuch_bool_t entire_thread)
{
notmuch_message_t *message;
notmuch_bool_t match;
+ int first_set = 1;
int next_indent;
+ fputs (format->message_set_start, stdout);
+
for (;
notmuch_messages_has_more (messages);
notmuch_messages_advance (messages))
{
+ if (!first_set)
+ fputs (format->message_set_sep, stdout);
+ first_set = 0;
+
+ fputs (format->message_set_start, stdout);
+
message = notmuch_messages_get (messages);
match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
@@ -202,15 +371,21 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent,
next_indent = indent;
if (match || entire_thread) {
- show_message (ctx, message, indent);
+ show_message (ctx, format, message, indent);
next_indent = indent + 1;
+
+ fputs (format->message_set_sep, stdout);
}
- show_messages (ctx, notmuch_message_get_replies (message),
+ show_messages (ctx, format, notmuch_message_get_replies (message),
next_indent, entire_thread);
notmuch_message_destroy (message);
+
+ fputs (format->message_set_end, stdout);
}
+
+ fputs (format->message_set_end, stdout);
}
int
@@ -223,15 +398,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
notmuch_thread_t *thread;
notmuch_messages_t *messages;
char *query_string;
+ char *opt;
+ const show_format_t *format = &format_text;
int entire_thread = 0;
int i;
+ int first_toplevel = 1;
for (i = 0; i < argc && argv[i][0] == '-'; i++) {
if (strcmp (argv[i], "--") == 0) {
i++;
break;
}
- if (strcmp(argv[i], "--entire-thread") == 0) {
+ if (STRNCMP_LITERAL (argv[i], "--format=") == 0) {
+ opt = argv[i] + sizeof ("--format=") - 1;
+ if (strcmp (opt, "text") == 0) {
+ format = &format_text;
+ } else if (strcmp (opt, "json") == 0) {
+ format = &format_json;
+ entire_thread = 1;
+ } else {
+ fprintf (stderr, "Invalid value for --format: %s\n", opt);
+ return 1;
+ }
+ } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
entire_thread = 1;
} else {
fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
@@ -268,6 +457,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
return 1;
}
+ fputs (format->message_set_start, stdout);
+
for (threads = notmuch_query_search_threads (query);
notmuch_threads_has_more (threads);
notmuch_threads_advance (threads))
@@ -280,11 +471,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
notmuch_thread_get_thread_id (thread));
- show_messages (ctx, messages, 0, entire_thread);
+ if (!first_toplevel)
+ fputs (format->message_set_sep, stdout);
+ first_toplevel = 0;
+
+ show_messages (ctx, format, messages, 0, entire_thread);
notmuch_thread_destroy (thread);
+
}
+ fputs (format->message_set_end, stdout);
+
notmuch_query_destroy (query);
notmuch_database_close (notmuch);