reply: Add a JSON reply format.

This new JSON format for replies includes headers generated for a
reply message as well as the headers of the original message.  Using
this data, a client can intelligently create a reply. For example, the
emacs client will be able to create replies with quoted HTML parts by
parsing the HTML parts.
This commit is contained in:
Adam Wolfe Gordon 2012-03-18 10:32:36 -06:00 committed by David Bremner
parent 06a34f1407
commit 1904b01b96
4 changed files with 79 additions and 12 deletions

View file

@ -62,7 +62,7 @@
#define STRINGIFY(s) STRINGIFY_(s) #define STRINGIFY(s) STRINGIFY_(s)
#define STRINGIFY_(s) #s #define STRINGIFY_(s) #s
struct mime_node; typedef struct mime_node mime_node_t;
struct notmuch_show_params; struct notmuch_show_params;
typedef struct notmuch_show_format { typedef struct notmuch_show_format {
@ -191,6 +191,12 @@ show_message_body (notmuch_message_t *message,
notmuch_status_t notmuch_status_t
show_one_part (const char *filename, int part); show_one_part (const char *filename, int part);
void
format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first);
void
format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply);
char * char *
json_quote_chararray (const void *ctx, const char *str, const size_t len); json_quote_chararray (const void *ctx, const char *str, const size_t len);
@ -288,7 +294,7 @@ debugger_is_active (void);
* parts. Message-type parts have one child, multipart-type parts * parts. Message-type parts have one child, multipart-type parts
* have multiple children, and leaf parts have zero children. * have multiple children, and leaf parts have zero children.
*/ */
typedef struct mime_node { struct mime_node {
/* The MIME object of this part. This will be a GMimeMessage, /* The MIME object of this part. This will be a GMimeMessage,
* GMimePart, GMimeMultipart, or a subclass of one of these. * GMimePart, GMimeMultipart, or a subclass of one of these.
* *
@ -351,7 +357,7 @@ typedef struct mime_node {
* number to assign it (or -1 if unknown). */ * number to assign it (or -1 if unknown). */
int next_child; int next_child;
int next_part_num; int next_part_num;
} mime_node_t; };
/* Construct a new MIME node pointing to the root message part of /* Construct a new MIME node pointing to the root message part of
* message. If cryptoctx is non-NULL, it will be used to verify * message. If cryptoctx is non-NULL, it will be used to verify

View file

@ -604,6 +604,51 @@ notmuch_reply_format_default(void *ctx,
return 0; return 0;
} }
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)
{
GMimeMessage *reply;
notmuch_messages_t *messages;
notmuch_message_t *message;
mime_node_t *node;
if (notmuch_query_count_messages (query) != 1) {
fprintf (stderr, "Error: search term did not match precisely one message.\n");
return 1;
}
messages = notmuch_query_search_messages (query);
message = notmuch_messages_get (messages);
if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt,
&node) != NOTMUCH_STATUS_SUCCESS)
return 1;
reply = create_reply_message (ctx, config, message, reply_all);
if (!reply)
return 1;
/* The headers of the reply message we've created */
printf ("{\"reply-headers\": ");
format_headers_json (ctx, reply, TRUE);
g_object_unref (G_OBJECT (reply));
reply = NULL;
/* Start the original */
printf (", \"original\": ");
format_part_json (ctx, node, TRUE);
/* End */
printf ("}\n");
notmuch_message_destroy (message);
return 0;
}
/* This format is currently tuned for a git send-email --notmuch hook */ /* This format is currently tuned for a git send-email --notmuch hook */
static int static int
notmuch_reply_format_headers_only(void *ctx, notmuch_reply_format_headers_only(void *ctx,
@ -666,6 +711,7 @@ notmuch_reply_format_headers_only(void *ctx,
enum { enum {
FORMAT_DEFAULT, FORMAT_DEFAULT,
FORMAT_JSON,
FORMAT_HEADERS_ONLY, FORMAT_HEADERS_ONLY,
}; };
@ -685,6 +731,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
notmuch_opt_desc_t options[] = { notmuch_opt_desc_t options[] = {
{ NOTMUCH_OPT_KEYWORD, &format, "format", 'f', { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
(notmuch_keyword_t []){ { "default", FORMAT_DEFAULT }, (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
{ "json", FORMAT_JSON },
{ "headers-only", FORMAT_HEADERS_ONLY }, { "headers-only", FORMAT_HEADERS_ONLY },
{ 0, 0 } } }, { 0, 0 } } },
{ NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r', { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
@ -703,6 +750,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
if (format == FORMAT_HEADERS_ONLY) if (format == FORMAT_HEADERS_ONLY)
reply_format_func = notmuch_reply_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
reply_format_func = notmuch_reply_format_default; reply_format_func = notmuch_reply_format_default;

View file

@ -200,8 +200,8 @@ _is_from_line (const char *line)
return 0; return 0;
} }
static void void
format_headers_json (const void *ctx, GMimeMessage *message) format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply)
{ {
void *local = talloc_new (ctx); void *local = talloc_new (ctx);
InternetAddressList *recipients; InternetAddressList *recipients;
@ -225,9 +225,22 @@ format_headers_json (const void *ctx, GMimeMessage *message)
printf (", %s: %s", printf (", %s: %s",
json_quote_str (local, "Cc"), json_quote_str (local, "Cc"),
json_quote_str (local, recipients_string)); json_quote_str (local, recipients_string));
printf (", %s: %s}",
if (reply) {
printf (", %s: %s",
json_quote_str (local, "In-reply-to"),
json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to")));
printf (", %s: %s",
json_quote_str (local, "References"),
json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "References")));
} else {
printf (", %s: %s",
json_quote_str (local, "Date"), json_quote_str (local, "Date"),
json_quote_str (local, g_mime_message_get_date_as_string (message))); json_quote_str (local, g_mime_message_get_date_as_string (message)));
}
printf ("}");
talloc_free (local); talloc_free (local);
} }
@ -538,7 +551,7 @@ format_part_text (const void *ctx, mime_node_t *node,
return NOTMUCH_STATUS_SUCCESS; return NOTMUCH_STATUS_SUCCESS;
} }
static void void
format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first)
{ {
/* Any changes to the JSON format should be reflected in the file /* Any changes to the JSON format should be reflected in the file
@ -549,7 +562,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first)
format_message_json (ctx, node->envelope_file); format_message_json (ctx, node->envelope_file);
printf ("\"headers\": "); printf ("\"headers\": ");
format_headers_json (ctx, GMIME_MESSAGE (node->part)); format_headers_json (ctx, GMIME_MESSAGE (node->part), FALSE);
printf (", \"body\": ["); printf (", \"body\": [");
format_part_json (ctx, mime_node_child (node, 0), first); format_part_json (ctx, mime_node_child (node, 0), first);
@ -623,7 +636,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first)
} else if (GMIME_IS_MESSAGE (node->part)) { } else if (GMIME_IS_MESSAGE (node->part)) {
printf (", \"content\": [{"); printf (", \"content\": [{");
printf ("\"headers\": "); printf ("\"headers\": ");
format_headers_json (local, GMIME_MESSAGE (node->part)); format_headers_json (local, GMIME_MESSAGE (node->part), FALSE);
printf (", \"body\": ["); printf (", \"body\": [");
terminator = "]}]"; terminator = "]}]";

View file

@ -613,7 +613,6 @@ EOF
test_expect_equal_file OUTPUT EXPECTED test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "'notmuch reply' to a multipart message with json format" test_begin_subtest "'notmuch reply' to a multipart message with json format"
test_subtest_known_broken
notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT
cat <<EOF >EXPECTED cat <<EOF >EXPECTED
{"reply-headers": {"Subject": "Re: Multipart message", {"reply-headers": {"Subject": "Re: Multipart message",