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.
This commit is contained in:
Scott Robinson 2009-12-31 11:17:40 -04:00 committed by Carl Worth
parent 3ca7a4fbcc
commit 6ce2bf68f5
8 changed files with 514 additions and 65 deletions

View file

@ -18,7 +18,8 @@ notmuch_client_srcs = \
notmuch-tag.c \ notmuch-tag.c \
notmuch-time.c \ notmuch-time.c \
query-string.c \ query-string.c \
show-message.c show-message.c \
json.c
notmuch_client_modules = $(notmuch_client_srcs:.c=.o) notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
notmuch: $(notmuch_client_modules) lib/notmuch.a notmuch: $(notmuch_client_modules) lib/notmuch.a

72
json.c Normal file
View file

@ -0,0 +1,72 @@
/* notmuch - Not much of an email program, (just index and search)
*
* Copyright © 2009 Scott Robinson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/ .
*
* Authors: Scott Robinson <scott@quadhome.com>
*
*/
#include "notmuch-client.h"
/*
* json_quote_str derived from cJSON's print_string_ptr,
* Copyright (c) 2009 Dave Gamble
*/
char *
json_quote_str(const void *ctx, const char *str)
{
const char *ptr;
char *ptr2;
char *out;
int len = 0;
if (!str)
return NULL;
for (ptr = str; *ptr; len++, ptr++) {
if (*ptr < 32 || *ptr == '\"' || *ptr == '\\')
len++;
}
out = talloc_array (ctx, char, len + 3);
ptr = str;
ptr2 = out;
*ptr2++ = '\"';
while (*ptr) {
if (*ptr > 31 && *ptr != '\"' && *ptr != '\\') {
*ptr2++ = *ptr++;
} else {
*ptr2++ = '\\';
switch (*ptr++) {
case '\"': *ptr2++ = '\"'; break;
case '\\': *ptr2++ = '\\'; break;
case '\b': *ptr2++ = 'b'; break;
case '\f': *ptr2++ = 'f'; break;
case '\n': *ptr2++ = 'n'; break;
case '\r': *ptr2++ = 'r'; break;
case '\t': *ptr2++ = 't'; break;
default: ptr2--; break;
}
}
}
*ptr2++ = '\"';
*ptr2++ = '\0';
return out;
}

View file

@ -123,6 +123,9 @@ notmuch_status_t
show_message_body (const char *filename, show_message_body (const char *filename,
void (*show_part) (GMimeObject *part, int *part_count)); void (*show_part) (GMimeObject *part, int *part_count));
char *
json_quote_str (const void *ctx, const char *str);
/* notmuch-config.c */ /* notmuch-config.c */
typedef struct _notmuch_config notmuch_config_t; typedef struct _notmuch_config notmuch_config_t;

View file

@ -20,8 +20,120 @@
#include "notmuch-client.h" #include "notmuch-client.h"
typedef struct search_format {
const char *results_start;
const char *thread_start;
void (*thread) (const void *ctx,
const char *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 *thread_sep;
const char *thread_end;
const char *results_end;
} search_format_t;
static void
format_thread_text (const void *ctx,
const char *id,
const time_t date,
const int matched,
const int total,
const char *authors,
const char *subject);
static const search_format_t format_text = {
"",
"",
format_thread_text,
" (",
"%s", " ",
")", "",
"\n",
"",
};
static void
format_thread_json (const void *ctx,
const char *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_thread_json,
"\"tags\": [",
"\"%s\"", ", ",
"]", ",\n",
"}",
"]\n",
};
static void
format_thread_text (const void *ctx,
const char *id,
const time_t date,
const int matched,
const int total,
const char *authors,
const char *subject)
{
printf ("thread:%s %12s [%d/%d] %s; %s",
id,
notmuch_time_relative_date (ctx, date),
matched,
total,
authors,
subject);
}
static void
format_thread_json (const void *ctx,
const char *id,
const time_t date,
const int matched,
const int total,
const char *authors,
const char *subject)
{
struct tm *tm;
char timestamp[40];
void *ctx_quote = talloc_new (ctx);
tm = gmtime (&date);
if (tm == NULL)
INTERNAL_ERROR ("gmtime failed on thread %s.", id);
if (strftime (timestamp, sizeof (timestamp), "%s", tm) == 0)
INTERNAL_ERROR ("strftime failed on thread %s.", id);
printf ("\"id\": %s,\n"
"\"timestamp\": %s,\n"
"\"matched\": %d,\n"
"\"total\": %d,\n"
"\"authors\": %s,\n"
"\"subject\": %s,\n",
json_quote_str (ctx_quote, id),
timestamp,
matched,
total,
json_quote_str (ctx_quote, authors),
json_quote_str (ctx_quote, subject));
talloc_free (ctx_quote);
}
static void static void
do_search_threads (const void *ctx, do_search_threads (const void *ctx,
const search_format_t *format,
notmuch_query_t *query, notmuch_query_t *query,
notmuch_sort_t sort) notmuch_sort_t sort)
{ {
@ -29,7 +141,9 @@ do_search_threads (const void *ctx,
notmuch_threads_t *threads; notmuch_threads_t *threads;
notmuch_tags_t *tags; notmuch_tags_t *tags;
time_t date; time_t date;
const char *relative_date; int first_thread = 1;
fputs (format->results_start, stdout);
for (threads = notmuch_query_search_threads (query); for (threads = notmuch_query_search_threads (query);
notmuch_threads_has_more (threads); notmuch_threads_has_more (threads);
@ -37,6 +151,9 @@ do_search_threads (const void *ctx,
{ {
int first_tag = 1; int first_tag = 1;
if (! first_thread)
fputs (format->thread_sep, stdout);
thread = notmuch_threads_get (threads); thread = notmuch_threads_get (threads);
if (sort == NOTMUCH_SORT_OLDEST_FIRST) if (sort == NOTMUCH_SORT_OLDEST_FIRST)
@ -44,30 +161,37 @@ do_search_threads (const void *ctx,
else else
date = notmuch_thread_get_newest_date (thread); date = notmuch_thread_get_newest_date (thread);
relative_date = notmuch_time_relative_date (ctx, date); fputs (format->thread_start, stdout);
printf ("thread:%s %12s [%d/%d] %s; %s", format->thread (ctx,
notmuch_thread_get_thread_id (thread), notmuch_thread_get_thread_id (thread),
relative_date, date,
notmuch_thread_get_matched_messages (thread), notmuch_thread_get_matched_messages (thread),
notmuch_thread_get_total_messages (thread), notmuch_thread_get_total_messages (thread),
notmuch_thread_get_authors (thread), notmuch_thread_get_authors (thread),
notmuch_thread_get_subject (thread)); notmuch_thread_get_subject (thread));
fputs (format->tag_start, stdout);
printf (" (");
for (tags = notmuch_thread_get_tags (thread); for (tags = notmuch_thread_get_tags (thread);
notmuch_tags_has_more (tags); notmuch_tags_has_more (tags);
notmuch_tags_advance (tags)) notmuch_tags_advance (tags))
{ {
if (! first_tag) if (! first_tag)
printf (" "); fputs (format->tag_sep, stdout);
printf ("%s", notmuch_tags_get (tags)); printf (format->tag, notmuch_tags_get (tags));
first_tag = 0; first_tag = 0;
} }
printf (")\n");
fputs (format->tag_end, stdout);
fputs (format->thread_end, stdout);
first_thread = 0;
notmuch_thread_destroy (thread); notmuch_thread_destroy (thread);
} }
fputs (format->results_end, stdout);
} }
int int
@ -79,6 +203,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
char *query_str; char *query_str;
char *opt; char *opt;
notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST; notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
const search_format_t *format = &format_text;
int i; int i;
for (i = 0; i < argc && argv[i][0] == '-'; i++) { for (i = 0; i < argc && argv[i][0] == '-'; i++) {
@ -96,6 +221,16 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
fprintf (stderr, "Invalid value for --sort: %s\n", opt); fprintf (stderr, "Invalid value for --sort: %s\n", opt);
return 1; return 1;
} }
} else 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;
} else {
fprintf (stderr, "Invalid value for --format: %s\n", opt);
return 1;
}
} else { } else {
fprintf (stderr, "Unrecognized option: %s\n", argv[i]); fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
return 1; return 1;
@ -132,7 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
notmuch_query_set_sort (query, sort); notmuch_query_set_sort (query, sort);
do_search_threads (ctx, query, sort); do_search_threads (ctx, format, query, sort);
notmuch_query_destroy (query); notmuch_query_destroy (query);
notmuch_database_close (notmuch); notmuch_database_close (notmuch);

View file

@ -20,8 +20,65 @@
#include "notmuch-client.h" #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 * 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; notmuch_tags_t *tags;
int first = 1; 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. */ /* Get a nice, single-line summary of message. */
static const char * 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; const char *from;
time_t date; time_t date;
@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)
} }
static void 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; GMimeStream *stream_filter = NULL;
GMimeDataWrapper *wrapper; GMimeDataWrapper *wrapper;
const char *charset; const char *charset;
charset = g_mime_object_get_content_type_parameter (part, "charset"); charset = g_mime_object_get_content_type_parameter (part, "charset");
if (stream_stdout) { if (stream_out) {
g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); stream_filter = g_mime_stream_filter_new(stream_out);
stream_filter = g_mime_stream_filter_new(stream_stdout);
g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter), g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
g_mime_filter_crlf_new(FALSE, FALSE)); g_mime_filter_crlf_new(FALSE, FALSE));
if (charset) { if (charset) {
@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)
g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
if (stream_filter) if (stream_filter)
g_object_unref(stream_filter); g_object_unref(stream_filter);
if (stream_stdout)
g_object_unref(stream_stdout);
} }
static void static void
show_part (GMimeObject *part, int *part_count) format_part_text (GMimeObject *part, int *part_count)
{ {
GMimeContentDisposition *disposition; GMimeContentDisposition *disposition;
GMimeContentType *content_type; 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); disposition = g_mime_object_get_content_disposition (part);
if (disposition && if (disposition &&
@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)
if (g_mime_content_type_is_type (content_type, "text", "*") && if (g_mime_content_type_is_type (content_type, "text", "*") &&
!g_mime_content_type_is_type (content_type, "text", "html")) !g_mime_content_type_is_type (content_type, "text", "html"))
{ {
show_part_content (part); show_part_content (part, stream_stdout);
} }
printf ("\fattachment}\n"); printf ("\fattachment}\n");
if (stream_stdout)
g_object_unref(stream_stdout);
return; return;
} }
@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)
if (g_mime_content_type_is_type (content_type, "text", "*") && if (g_mime_content_type_is_type (content_type, "text", "*") &&
!g_mime_content_type_is_type (content_type, "text", "html")) !g_mime_content_type_is_type (content_type, "text", "html"))
{ {
show_part_content (part); show_part_content (part, stream_stdout);
} }
else else
{ {
@ -144,57 +274,96 @@ show_part (GMimeObject *part, int *part_count)
} }
printf ("\fpart}\n"); printf ("\fpart}\n");
if (stream_stdout)
g_object_unref(stream_stdout);
} }
static void static void
show_message (void *ctx, notmuch_message_t *message, int indent) format_part_json (GMimeObject *part, int *part_count)
{ {
const char *headers[] = { GMimeContentType *content_type;
"Subject", "From", "To", "Cc", "Bcc", "Date" GMimeContentDisposition *disposition;
}; void *ctx = talloc_new (NULL);
const char *name, *value; GMimeStream *stream_memory = g_mime_stream_mem_new ();
unsigned int i; GByteArray *part_content;
printf ("\fmessage{ id:%s depth:%d match:%d filename:%s\n", content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
notmuch_message_get_message_id (message),
indent,
notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
notmuch_message_get_filename (message));
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++) { disposition = g_mime_object_get_content_disposition (part);
name = headers[i]; if (disposition &&
value = notmuch_message_get_header (message, name); strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
if (value) {
printf ("%s: %s\n", name, value); const char *filename = g_mime_part_get_filename (GMIME_PART (part));
printf (", \"filename\": %s", json_quote_str (ctx, filename));
} }
printf ("\fheader}\n"); if (g_mime_content_type_is_type (content_type, "text", "*") &&
printf ("\fbody{\n"); !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));
show_message_body (notmuch_message_get_filename (message), show_part); printf (", \"content\": %s", json_quote_str (ctx, (char *) part_content->data));
}
printf ("\fbody}\n"); fputs ("}", stdout);
printf ("\fmessage}\n"); 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);
fputs (format->header_start, stdout);
if (format->header)
format->header(ctx, message);
fputs (format->header_end, stdout);
fputs (format->body_start, stdout);
if (format->part)
show_message_body (notmuch_message_get_filename (message), format->part);
fputs (format->body_end, stdout);
fputs (format->message_end, stdout);
} }
static void 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_bool_t entire_thread)
{ {
notmuch_message_t *message; notmuch_message_t *message;
notmuch_bool_t match; notmuch_bool_t match;
int first_set = 1;
int next_indent; int next_indent;
fputs (format->message_set_start, stdout);
for (; for (;
notmuch_messages_has_more (messages); notmuch_messages_has_more (messages);
notmuch_messages_advance (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); message = notmuch_messages_get (messages);
match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH); 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; next_indent = indent;
if (match || entire_thread) { if (match || entire_thread) {
show_message (ctx, message, indent); show_message (ctx, format, message, indent);
next_indent = indent + 1; 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); next_indent, entire_thread);
notmuch_message_destroy (message); notmuch_message_destroy (message);
fputs (format->message_set_end, stdout);
} }
fputs (format->message_set_end, stdout);
} }
int int
@ -223,15 +398,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
notmuch_thread_t *thread; notmuch_thread_t *thread;
notmuch_messages_t *messages; notmuch_messages_t *messages;
char *query_string; char *query_string;
char *opt;
const show_format_t *format = &format_text;
int entire_thread = 0; int entire_thread = 0;
int i; int i;
int first_toplevel = 1;
for (i = 0; i < argc && argv[i][0] == '-'; i++) { for (i = 0; i < argc && argv[i][0] == '-'; i++) {
if (strcmp (argv[i], "--") == 0) { if (strcmp (argv[i], "--") == 0) {
i++; i++;
break; 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; entire_thread = 1;
} else { } else {
fprintf (stderr, "Unrecognized option: %s\n", argv[i]); 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; return 1;
} }
fputs (format->message_set_start, stdout);
for (threads = notmuch_query_search_threads (query); for (threads = notmuch_query_search_threads (query);
notmuch_threads_has_more (threads); notmuch_threads_has_more (threads);
notmuch_threads_advance (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", INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
notmuch_thread_get_thread_id (thread)); 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); notmuch_thread_destroy (thread);
} }
fputs (format->message_set_end, stdout);
notmuch_query_destroy (query); notmuch_query_destroy (query);
notmuch_database_close (notmuch); notmuch_database_close (notmuch);

View file

@ -146,6 +146,12 @@ Supported options for
include include
.RS 4 .RS 4
.TP 4 .TP 4
.BR \-\-format= ( json | text )
Presents the results in either JSON or plain-text (default).
.RE
.RS 4
.TP 4
.BR \-\-sort= ( newest\-first | oldest\-first ) .BR \-\-sort= ( newest\-first | oldest\-first )
This option can be used to present results in either chronological order This option can be used to present results in either chronological order
@ -194,7 +200,14 @@ matched message will be displayed.
.RE .RE
.RS 4 .RS 4
The output format is plain-text, with all text-content MIME parts .TP 4
.B \-\-format=(json|text)
.RS 4
.TP 4
.B text
The default plain-text format has text-content MIME parts
decoded. Various components in the output, decoded. Various components in the output,
.RB ( message ", " header ", " body ", " attachment ", and MIME " part ), .RB ( message ", " header ", " body ", " attachment ", and MIME " part ),
will be delimited by easily-parsed markers. Each marker consists of a will be delimited by easily-parsed markers. Each marker consists of a
@ -202,6 +215,18 @@ Control-L character (ASCII decimal 12), the name of the marker, and
then either an opening or closing brace, ('{' or '}'), to either open then either an opening or closing brace, ('{' or '}'), to either open
or close the component. or close the component.
.RE
.RS 4
.TP 4
.B json
Format output as Javascript Object Notation (JSON). JSON output always
includes all messages in a matching thread; in effect
.B \-\-format=json
implies
.B \-\-entire\-thread
.RE
A common use of A common use of
.B notmuch show .B notmuch show
is to display a single thread of email messages. For this, use a is to display a single thread of email messages. For this, use a

View file

@ -157,6 +157,11 @@ command_t commands[] = {
"\n" "\n"
"\t\tSupported options for search include:\n" "\t\tSupported options for search include:\n"
"\n" "\n"
"\t\t--format=(json|text)\n"
"\n"
"\t\t\tPresents the results in either JSON or\n"
"\t\t\tplain-text (default)\n"
"\n"
"\t\t--sort=(newest-first|oldest-first)\n" "\t\t--sort=(newest-first|oldest-first)\n"
"\n" "\n"
"\t\t\tPresent results in either chronological order\n" "\t\t\tPresent results in either chronological order\n"
@ -181,13 +186,23 @@ command_t commands[] = {
"\t\t\tall messages in the same thread as any matched\n" "\t\t\tall messages in the same thread as any matched\n"
"\t\t\tmessage will be displayed.\n" "\t\t\tmessage will be displayed.\n"
"\n" "\n"
"\t\tThe output format is plain-text, with all text-content\n" "\t\t--format=(json|text)\n"
"\t\tMIME parts decoded. Various components in the output,\n" "\n"
"\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n" "\t\t\ttext\t(default)\n"
"\t\tare delimited by easily-parsed markers. Each marker consists\n" "\n"
"\t\tof a Control-L character (ASCII decimal 12), the name of\n" "\t\t\tThe plain-text has all text-content MIME parts decoded.\n"
"\t\tthe marker, and then either an opening or closing brace,\n" "\t\t\tVarious components in the output, ('message', 'header',\n"
"\t\t'{' or '}' to either open or close the component.\n" "\t\t\t'body', 'attachment', and MIME 'part') are delimited by\n"
"\t\t\teasily-parsed markers. Each marker consists of a Control-L\n"
"\t\t\tcharacter (ASCII decimal 12), the name of the marker, and\n"
"\t\t\tthen either an opening or closing brace, '{' or '}' to\n"
"\t\t\teither open or close the component.\n"
"\n"
"\t\t\tjson\n"
"\n"
"\t\t\tFormat output as Javascript Object Notation (JSON).\n"
"\t\t\tJSON output always includes all messages in a matching,\n"
"\t\t\tthread i.e. '--output=json' implies '--entire-thread'\n"
"\n" "\n"
"\t\tA common use of \"notmuch show\" is to display a single\n" "\t\tA common use of \"notmuch show\" is to display a single\n"
"\t\tthread of email messages. For this, use a search term of\n" "\t\tthread of email messages. For this, use a search term of\n"

View file

@ -26,8 +26,6 @@ static void
show_message_part (GMimeObject *part, int *part_count, show_message_part (GMimeObject *part, int *part_count,
void (*show_part) (GMimeObject *part, int *part_count)) void (*show_part) (GMimeObject *part, int *part_count))
{ {
*part_count = *part_count + 1;
if (GMIME_IS_MULTIPART (part)) { if (GMIME_IS_MULTIPART (part)) {
GMimeMultipart *multipart = GMIME_MULTIPART (part); GMimeMultipart *multipart = GMIME_MULTIPART (part);
int i; int i;
@ -56,6 +54,8 @@ show_message_part (GMimeObject *part, int *part_count,
return; return;
} }
*part_count = *part_count + 1;
(*show_part) (part, part_count); (*show_part) (part, part_count);
} }