mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-21 18:38:08 +01:00
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:
parent
06a34f1407
commit
1904b01b96
4 changed files with 79 additions and 12 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 = "]}]";
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue