1 Return-Path: <bremner@pivot.cs.unb.ca>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id AF715431FBC
\r
6 for <notmuch@notmuchmail.org>; Thu, 31 Dec 2009 07:19:52 -0800 (PST)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
8 Received: from olra.theworths.org ([127.0.0.1])
\r
9 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
10 with ESMTP id 554aUXUBRgQV for <notmuch@notmuchmail.org>;
\r
11 Thu, 31 Dec 2009 07:19:50 -0800 (PST)
\r
12 Received: from pivot.cs.unb.ca (pivot.cs.unb.ca [131.202.240.57])
\r
13 by olra.theworths.org (Postfix) with ESMTP id 10DBE431FAE
\r
14 for <notmuch@notmuchmail.org>; Thu, 31 Dec 2009 07:19:50 -0800 (PST)
\r
16 fctnnbsc30w-142167182194.pppoe-dynamic.high-speed.nb.bellaliant.net
\r
17 ([142.167.182.194] helo=rocinante.cs.unb.ca)
\r
18 by pivot.cs.unb.ca with esmtpsa (TLS1.0:RSA_AES_256_CBC_SHA1:32)
\r
19 (Exim 4.69) (envelope-from <bremner@pivot.cs.unb.ca>)
\r
20 id 1NQMoW-0000VT-Ef; Thu, 31 Dec 2009 11:19:49 -0400
\r
21 Received: from bremner by rocinante.cs.unb.ca with local (Exim 4.71)
\r
22 (envelope-from <bremner@rocinante.cs.unb.ca>)
\r
23 id 1NQMmd-0004bv-6y; Thu, 31 Dec 2009 11:17:51 -0400
\r
24 From: david@tethera.net
\r
25 To: notmuch@notmuchmail.org
\r
26 Date: Thu, 31 Dec 2009 11:17:40 -0400
\r
27 Message-Id: <1262272660-16786-1-git-send-email-david@tethera.net>
\r
28 X-Mailer: git-send-email 1.6.5
\r
29 In-Reply-To: <id:1262249616-sup-3911@elise>
\r
30 References: <id:1262249616-sup-3911@elise>
\r
31 X-Sender-Verified: bremner@pivot.cs.unb.ca
\r
32 Subject: [notmuch] [PATCH] Add an "--format=(json|text)" command-line option
\r
33 to both notmuch-search and notmuch-show.
\r
34 X-BeenThere: notmuch@notmuchmail.org
\r
35 X-Mailman-Version: 2.1.12
\r
37 List-Id: "Use and development of the notmuch mail system."
\r
38 <notmuch.notmuchmail.org>
\r
39 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
40 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
41 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
42 List-Post: <mailto:notmuch@notmuchmail.org>
\r
43 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
44 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
45 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
46 X-List-Received-Date: Thu, 31 Dec 2009 15:19:52 -0000
\r
48 From: Scott Robinson <scott@quadhome.com>
\r
50 In the case of notmuch-show, "--format=json" also implies
\r
51 "--entire-thread" as the thread structure is implicit in the emitted
\r
54 As a coincidence to the implementation, multipart message ID numbers are
\r
55 now incremented with each part printed. This changes the previous
\r
56 semantics, which were unclear and not necessary related to the actual
\r
57 ordering of the message parts.
\r
60 This is an updated version of Scott Robinson's patch of
\r
61 id:1261114167-sup-8228@lisa. All of the hard work is Scott's. My
\r
62 changes to Scotts patch are
\r
63 - change option to --format from --output
\r
64 - make a couple of calls to function points conditional on the
\r
65 function pointers being non-null
\r
66 - updated the built-in documentation and the man page
\r
68 I also changed the copyright of json.c to Scott. Hope that's OK with
\r
71 Makefile.local | 3 +-
\r
72 json.c | 72 ++++++++++++++
\r
73 notmuch-client.h | 3 +
\r
74 notmuch-search.c | 163 +++++++++++++++++++++++++++++---
\r
75 notmuch-show.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++--------
\r
76 notmuch.1 | 27 +++++-
\r
77 notmuch.c | 29 +++++--
\r
78 show-message.c | 4 +-
\r
79 8 files changed, 514 insertions(+), 65 deletions(-)
\r
80 create mode 100644 json.c
\r
82 diff --git a/Makefile.local b/Makefile.local
\r
83 index 933ff4c..53b474b 100644
\r
84 --- a/Makefile.local
\r
85 +++ b/Makefile.local
\r
86 @@ -18,7 +18,8 @@ notmuch_client_srcs = \
\r
94 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
\r
95 notmuch: $(notmuch_client_modules) lib/notmuch.a
\r
96 diff --git a/json.c b/json.c
\r
97 new file mode 100644
\r
98 index 0000000..3960750
\r
102 +/* notmuch - Not much of an email program, (just index and search)
\r
104 + * Copyright © 2009 Scott Robinson
\r
106 + * This program is free software: you can redistribute it and/or modify
\r
107 + * it under the terms of the GNU General Public License as published by
\r
108 + * the Free Software Foundation, either version 3 of the License, or
\r
109 + * (at your option) any later version.
\r
111 + * This program is distributed in the hope that it will be useful,
\r
112 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
113 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
114 + * GNU General Public License for more details.
\r
116 + * You should have received a copy of the GNU General Public License
\r
117 + * along with this program. If not, see http://www.gnu.org/licenses/ .
\r
119 + * Authors: Scott Robinson <scott@quadhome.com>
\r
123 +#include "notmuch-client.h"
\r
126 + * json_quote_str derived from cJSON's print_string_ptr,
\r
127 + * Copyright (c) 2009 Dave Gamble
\r
131 +json_quote_str(const void *ctx, const char *str)
\r
141 + for (ptr = str; *ptr; len++, ptr++) {
\r
142 + if (*ptr < 32 || *ptr == '\"' || *ptr == '\\')
\r
146 + out = talloc_array (ctx, char, len + 3);
\r
153 + if (*ptr > 31 && *ptr != '\"' && *ptr != '\\') {
\r
154 + *ptr2++ = *ptr++;
\r
157 + switch (*ptr++) {
\r
158 + case '\"': *ptr2++ = '\"'; break;
\r
159 + case '\\': *ptr2++ = '\\'; break;
\r
160 + case '\b': *ptr2++ = 'b'; break;
\r
161 + case '\f': *ptr2++ = 'f'; break;
\r
162 + case '\n': *ptr2++ = 'n'; break;
\r
163 + case '\r': *ptr2++ = 'r'; break;
\r
164 + case '\t': *ptr2++ = 't'; break;
\r
165 + default: ptr2--; break;
\r
174 diff --git a/notmuch-client.h b/notmuch-client.h
\r
175 index 50a30fe..7b844b9 100644
\r
176 --- a/notmuch-client.h
\r
177 +++ b/notmuch-client.h
\r
178 @@ -143,6 +143,9 @@ notmuch_status_t
\r
179 show_message_body (const char *filename,
\r
180 void (*show_part) (GMimeObject *part, int *part_count));
\r
183 +json_quote_str (const void *ctx, const char *str);
\r
185 /* notmuch-config.c */
\r
187 typedef struct _notmuch_config notmuch_config_t;
\r
188 diff --git a/notmuch-search.c b/notmuch-search.c
\r
189 index dc44eb6..482c6e8 100644
\r
190 --- a/notmuch-search.c
\r
191 +++ b/notmuch-search.c
\r
192 @@ -20,8 +20,120 @@
\r
194 #include "notmuch-client.h"
\r
196 +typedef struct search_format {
\r
197 + const char *results_start;
\r
198 + const char *thread_start;
\r
199 + void (*thread) (const void *ctx,
\r
201 + const time_t date,
\r
202 + const int matched,
\r
204 + const char *authors,
\r
205 + const char *subject);
\r
206 + const char *tag_start;
\r
208 + const char *tag_sep;
\r
209 + const char *tag_end;
\r
210 + const char *thread_sep;
\r
211 + const char *thread_end;
\r
212 + const char *results_end;
\r
213 +} search_format_t;
\r
216 +format_thread_text (const void *ctx,
\r
218 + const time_t date,
\r
219 + const int matched,
\r
221 + const char *authors,
\r
222 + const char *subject);
\r
223 +static const search_format_t format_text = {
\r
226 + format_thread_text,
\r
235 +format_thread_json (const void *ctx,
\r
237 + const time_t date,
\r
238 + const int matched,
\r
240 + const char *authors,
\r
241 + const char *subject);
\r
242 +static const search_format_t format_json = {
\r
245 + format_thread_json,
\r
254 +format_thread_text (const void *ctx,
\r
256 + const time_t date,
\r
257 + const int matched,
\r
259 + const char *authors,
\r
260 + const char *subject)
\r
262 + printf ("thread:%s %12s [%d/%d] %s; %s",
\r
264 + notmuch_time_relative_date (ctx, date),
\r
272 +format_thread_json (const void *ctx,
\r
274 + const time_t date,
\r
275 + const int matched,
\r
277 + const char *authors,
\r
278 + const char *subject)
\r
281 + char timestamp[40];
\r
282 + void *ctx_quote = talloc_new (ctx);
\r
284 + tm = gmtime (&date);
\r
286 + INTERNAL_ERROR ("gmtime failed on thread %s.", id);
\r
288 + if (strftime (timestamp, sizeof (timestamp), "%s", tm) == 0)
\r
289 + INTERNAL_ERROR ("strftime failed on thread %s.", id);
\r
291 + printf ("\"id\": %s,\n"
\r
292 + "\"timestamp\": %s,\n"
\r
293 + "\"matched\": %d,\n"
\r
294 + "\"total\": %d,\n"
\r
295 + "\"authors\": %s,\n"
\r
296 + "\"subject\": %s,\n",
\r
297 + json_quote_str (ctx_quote, id),
\r
301 + json_quote_str (ctx_quote, authors),
\r
302 + json_quote_str (ctx_quote, subject));
\r
304 + talloc_free (ctx_quote);
\r
308 do_search_threads (const void *ctx,
\r
309 + const search_format_t *format,
\r
310 notmuch_query_t *query,
\r
311 notmuch_sort_t sort)
\r
313 @@ -29,7 +141,9 @@ do_search_threads (const void *ctx,
\r
314 notmuch_threads_t *threads;
\r
315 notmuch_tags_t *tags;
\r
317 - const char *relative_date;
\r
318 + int first_thread = 1;
\r
320 + fputs (format->results_start, stdout);
\r
322 for (threads = notmuch_query_search_threads (query);
\r
323 notmuch_threads_has_more (threads);
\r
324 @@ -37,6 +151,9 @@ do_search_threads (const void *ctx,
\r
328 + if (! first_thread)
\r
329 + fputs (format->thread_sep, stdout);
\r
331 thread = notmuch_threads_get (threads);
\r
333 if (sort == NOTMUCH_SORT_OLDEST_FIRST)
\r
334 @@ -44,30 +161,37 @@ do_search_threads (const void *ctx,
\r
336 date = notmuch_thread_get_newest_date (thread);
\r
338 - relative_date = notmuch_time_relative_date (ctx, date);
\r
339 + fputs (format->thread_start, stdout);
\r
341 + format->thread (ctx,
\r
342 + notmuch_thread_get_thread_id (thread),
\r
344 + notmuch_thread_get_matched_messages (thread),
\r
345 + notmuch_thread_get_total_messages (thread),
\r
346 + notmuch_thread_get_authors (thread),
\r
347 + notmuch_thread_get_subject (thread));
\r
349 - printf ("thread:%s %12s [%d/%d] %s; %s",
\r
350 - notmuch_thread_get_thread_id (thread),
\r
352 - notmuch_thread_get_matched_messages (thread),
\r
353 - notmuch_thread_get_total_messages (thread),
\r
354 - notmuch_thread_get_authors (thread),
\r
355 - notmuch_thread_get_subject (thread));
\r
356 + fputs (format->tag_start, stdout);
\r
359 for (tags = notmuch_thread_get_tags (thread);
\r
360 notmuch_tags_has_more (tags);
\r
361 notmuch_tags_advance (tags))
\r
365 - printf ("%s", notmuch_tags_get (tags));
\r
366 + fputs (format->tag_sep, stdout);
\r
367 + printf (format->tag, notmuch_tags_get (tags));
\r
372 + fputs (format->tag_end, stdout);
\r
373 + fputs (format->thread_end, stdout);
\r
375 + first_thread = 0;
\r
377 notmuch_thread_destroy (thread);
\r
380 + fputs (format->results_end, stdout);
\r
384 @@ -79,6 +203,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
387 notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
\r
388 + const search_format_t *format = &format_text;
\r
391 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
392 @@ -96,6 +221,16 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
393 fprintf (stderr, "Invalid value for --sort: %s\n", opt);
\r
396 + } else if (STRNCMP_LITERAL (argv[i], "--format=") == 0) {
\r
397 + opt = argv[i] + sizeof ("--format=") - 1;
\r
398 + if (strcmp (opt, "text") == 0) {
\r
399 + format = &format_text;
\r
400 + } else if (strcmp (opt, "json") == 0) {
\r
401 + format = &format_json;
\r
403 + fprintf (stderr, "Invalid value for --format: %s\n", opt);
\r
407 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
409 @@ -132,7 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
411 notmuch_query_set_sort (query, sort);
\r
413 - do_search_threads (ctx, query, sort);
\r
414 + do_search_threads (ctx, format, query, sort);
\r
416 notmuch_query_destroy (query);
\r
417 notmuch_database_close (notmuch);
\r
418 diff --git a/notmuch-show.c b/notmuch-show.c
\r
419 index 376aacd..1a1d601 100644
\r
420 --- a/notmuch-show.c
\r
421 +++ b/notmuch-show.c
\r
424 #include "notmuch-client.h"
\r
426 +typedef struct show_format {
\r
427 + const char *message_set_start;
\r
428 + const char *message_start;
\r
429 + void (*message) (const void *ctx,
\r
430 + notmuch_message_t *message,
\r
432 + const char *header_start;
\r
433 + void (*header) (const void *ctx,
\r
434 + notmuch_message_t *message);
\r
435 + const char *header_end;
\r
436 + const char *body_start;
\r
437 + void (*part) (GMimeObject *part,
\r
438 + int *part_count);
\r
439 + const char *body_end;
\r
440 + const char *message_end;
\r
441 + const char *message_set_sep;
\r
442 + const char *message_set_end;
\r
446 +format_message_text (unused (const void *ctx),
\r
447 + notmuch_message_t *message,
\r
450 +format_headers_text (const void *ctx,
\r
451 + notmuch_message_t *message);
\r
453 +format_part_text (GMimeObject *part,
\r
454 + int *part_count);
\r
455 +static const show_format_t format_text = {
\r
457 + "\fmessage{ ", format_message_text,
\r
458 + "\fheader{\n", format_headers_text, "\fheader}\n",
\r
459 + "\fbody{\n", format_part_text, "\fbody}\n",
\r
460 + "\fmessage}\n", "",
\r
465 +format_message_json (const void *ctx,
\r
466 + notmuch_message_t *message,
\r
467 + unused (int indent));
\r
469 +format_headers_json (const void *ctx,
\r
470 + notmuch_message_t *message);
\r
472 +format_part_json (GMimeObject *part,
\r
473 + int *part_count);
\r
474 +static const show_format_t format_json = {
\r
476 + "{", format_message_json,
\r
477 + ", \"headers\": {", format_headers_json, "}",
\r
478 + ", \"body\": [", format_part_json, "]",
\r
483 static const char *
\r
484 -_get_tags_as_string (void *ctx, notmuch_message_t *message)
\r
485 +_get_tags_as_string (const void *ctx, notmuch_message_t *message)
\r
487 notmuch_tags_t *tags;
\r
489 @@ -48,7 +105,7 @@ _get_tags_as_string (void *ctx, notmuch_message_t *message)
\r
491 /* Get a nice, single-line summary of message. */
\r
492 static const char *
\r
493 -_get_one_line_summary (void *ctx, notmuch_message_t *message)
\r
494 +_get_one_line_summary (const void *ctx, notmuch_message_t *message)
\r
498 @@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)
\r
502 -show_part_content (GMimeObject *part)
\r
503 +format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)
\r
505 + printf ("id:%s depth:%d match:%d filename:%s\n",
\r
506 + notmuch_message_get_message_id (message),
\r
508 + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
\r
509 + notmuch_message_get_filename (message));
\r
513 +format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))
\r
515 + void *ctx_quote = talloc_new (ctx);
\r
517 + printf ("\"id\": %s, \"match\": %s, \"filename\": %s",
\r
518 + json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
\r
519 + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
\r
520 + json_quote_str (ctx_quote, notmuch_message_get_filename (message)));
\r
522 + talloc_free (ctx_quote);
\r
526 +format_headers_text (const void *ctx, notmuch_message_t *message)
\r
528 + const char *headers[] = {
\r
529 + "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
531 + const char *name, *value;
\r
534 + printf ("%s\n", _get_one_line_summary (ctx, message));
\r
536 + for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
537 + name = headers[i];
\r
538 + value = notmuch_message_get_header (message, name);
\r
540 + printf ("%s: %s\n", name, value);
\r
545 +format_headers_json (const void *ctx, notmuch_message_t *message)
\r
547 + const char *headers[] = {
\r
548 + "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
550 + const char *name, *value;
\r
552 + int first_header = 1;
\r
553 + void *ctx_quote = talloc_new (ctx);
\r
555 + for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
556 + name = headers[i];
\r
557 + value = notmuch_message_get_header (message, name);
\r
560 + if (!first_header)
\r
561 + fputs (", ", stdout);
\r
562 + first_header = 0;
\r
564 + printf ("%s: %s",
\r
565 + json_quote_str (ctx_quote, name),
\r
566 + json_quote_str (ctx_quote, value));
\r
570 + talloc_free (ctx_quote);
\r
574 +show_part_content (GMimeObject *part, GMimeStream *stream_out)
\r
576 - GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
\r
577 GMimeStream *stream_filter = NULL;
\r
578 GMimeDataWrapper *wrapper;
\r
579 const char *charset;
\r
581 charset = g_mime_object_get_content_type_parameter (part, "charset");
\r
583 - if (stream_stdout) {
\r
584 - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
585 - stream_filter = g_mime_stream_filter_new(stream_stdout);
\r
586 + if (stream_out) {
\r
587 + stream_filter = g_mime_stream_filter_new(stream_out);
\r
588 g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
\r
589 g_mime_filter_crlf_new(FALSE, FALSE));
\r
591 @@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)
\r
592 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
\r
594 g_object_unref(stream_filter);
\r
595 - if (stream_stdout)
\r
596 - g_object_unref(stream_stdout);
\r
600 -show_part (GMimeObject *part, int *part_count)
\r
601 +format_part_text (GMimeObject *part, int *part_count)
\r
603 GMimeContentDisposition *disposition;
\r
604 GMimeContentType *content_type;
\r
605 + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
\r
607 + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
609 disposition = g_mime_object_get_content_disposition (part);
\r
611 @@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)
\r
612 if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
613 !g_mime_content_type_is_type (content_type, "text", "html"))
\r
615 - show_part_content (part);
\r
616 + show_part_content (part, stream_stdout);
\r
619 printf ("\fattachment}\n");
\r
621 + if (stream_stdout)
\r
622 + g_object_unref(stream_stdout);
\r
627 @@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)
\r
628 if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
629 !g_mime_content_type_is_type (content_type, "text", "html"))
\r
631 - show_part_content (part);
\r
632 + show_part_content (part, stream_stdout);
\r
636 @@ -144,57 +274,96 @@ show_part (GMimeObject *part, int *part_count)
\r
639 printf ("\fpart}\n");
\r
641 + if (stream_stdout)
\r
642 + g_object_unref(stream_stdout);
\r
646 -show_message (void *ctx, notmuch_message_t *message, int indent)
\r
647 +format_part_json (GMimeObject *part, int *part_count)
\r
649 - const char *headers[] = {
\r
650 - "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
652 - const char *name, *value;
\r
654 + GMimeContentType *content_type;
\r
655 + GMimeContentDisposition *disposition;
\r
656 + void *ctx = talloc_new (NULL);
\r
657 + GMimeStream *stream_memory = g_mime_stream_mem_new ();
\r
658 + GByteArray *part_content;
\r
660 - printf ("\fmessage{ id:%s depth:%d match:%d filename:%s\n",
\r
661 - notmuch_message_get_message_id (message),
\r
663 - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
\r
664 - notmuch_message_get_filename (message));
\r
665 + content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
\r
667 - printf ("\fheader{\n");
\r
668 + if (*part_count > 1)
\r
669 + fputs (", ", stdout);
\r
671 - printf ("%s\n", _get_one_line_summary (ctx, message));
\r
672 + printf ("{\"id\": %d, \"content-type\": %s",
\r
674 + json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
\r
676 - for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
677 - name = headers[i];
\r
678 - value = notmuch_message_get_header (message, name);
\r
680 - printf ("%s: %s\n", name, value);
\r
681 + disposition = g_mime_object_get_content_disposition (part);
\r
682 + if (disposition &&
\r
683 + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
\r
685 + const char *filename = g_mime_part_get_filename (GMIME_PART (part));
\r
687 + printf (", \"filename\": %s", json_quote_str (ctx, filename));
\r
690 - printf ("\fheader}\n");
\r
691 - printf ("\fbody{\n");
\r
692 + if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
693 + !g_mime_content_type_is_type (content_type, "text", "html"))
\r
695 + show_part_content (part, stream_memory);
\r
696 + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
\r
698 + printf (", \"content\": %s", json_quote_str (ctx, (char *) part_content->data));
\r
701 + fputs ("}", stdout);
\r
703 + talloc_free (ctx);
\r
704 + if (stream_memory)
\r
705 + g_object_unref (stream_memory);
\r
709 +show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)
\r
711 + fputs (format->message_start, stdout);
\r
712 + if (format->message)
\r
713 + format->message(ctx, message, indent);
\r
715 - show_message_body (notmuch_message_get_filename (message), show_part);
\r
716 + fputs (format->header_start, stdout);
\r
717 + if (format->header)
\r
718 + format->header(ctx, message);
\r
719 + fputs (format->header_end, stdout);
\r
721 - printf ("\fbody}\n");
\r
722 + fputs (format->body_start, stdout);
\r
723 + if (format->part)
\r
724 + show_message_body (notmuch_message_get_filename (message), format->part);
\r
725 + fputs (format->body_end, stdout);
\r
727 - printf ("\fmessage}\n");
\r
728 + fputs (format->message_end, stdout);
\r
733 -show_messages (void *ctx, notmuch_messages_t *messages, int indent,
\r
734 +show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,
\r
735 notmuch_bool_t entire_thread)
\r
737 notmuch_message_t *message;
\r
738 notmuch_bool_t match;
\r
739 + int first_set = 1;
\r
742 + fputs (format->message_set_start, stdout);
\r
745 notmuch_messages_has_more (messages);
\r
746 notmuch_messages_advance (messages))
\r
749 + fputs (format->message_set_sep, stdout);
\r
752 + fputs (format->message_set_start, stdout);
\r
754 message = notmuch_messages_get (messages);
\r
756 match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
\r
757 @@ -202,15 +371,21 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent,
\r
758 next_indent = indent;
\r
760 if (match || entire_thread) {
\r
761 - show_message (ctx, message, indent);
\r
762 + show_message (ctx, format, message, indent);
\r
763 next_indent = indent + 1;
\r
765 + fputs (format->message_set_sep, stdout);
\r
768 - show_messages (ctx, notmuch_message_get_replies (message),
\r
769 + show_messages (ctx, format, notmuch_message_get_replies (message),
\r
770 next_indent, entire_thread);
\r
772 notmuch_message_destroy (message);
\r
774 + fputs (format->message_set_end, stdout);
\r
777 + fputs (format->message_set_end, stdout);
\r
781 @@ -223,15 +398,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
782 notmuch_thread_t *thread;
\r
783 notmuch_messages_t *messages;
\r
784 char *query_string;
\r
786 + const show_format_t *format = &format_text;
\r
787 int entire_thread = 0;
\r
789 + int first_toplevel = 1;
\r
791 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
792 if (strcmp (argv[i], "--") == 0) {
\r
796 - if (strcmp(argv[i], "--entire-thread") == 0) {
\r
797 + if (STRNCMP_LITERAL (argv[i], "--format=") == 0) {
\r
798 + opt = argv[i] + sizeof ("--format=") - 1;
\r
799 + if (strcmp (opt, "text") == 0) {
\r
800 + format = &format_text;
\r
801 + } else if (strcmp (opt, "json") == 0) {
\r
802 + format = &format_json;
\r
803 + entire_thread = 1;
\r
805 + fprintf (stderr, "Invalid value for --format: %s\n", opt);
\r
808 + } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
\r
811 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
812 @@ -268,6 +457,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
816 + fputs (format->message_set_start, stdout);
\r
818 for (threads = notmuch_query_search_threads (query);
\r
819 notmuch_threads_has_more (threads);
\r
820 notmuch_threads_advance (threads))
\r
821 @@ -280,11 +471,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
822 INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
\r
823 notmuch_thread_get_thread_id (thread));
\r
825 - show_messages (ctx, messages, 0, entire_thread);
\r
826 + if (!first_toplevel)
\r
827 + fputs (format->message_set_sep, stdout);
\r
828 + first_toplevel = 0;
\r
830 + show_messages (ctx, format, messages, 0, entire_thread);
\r
832 notmuch_thread_destroy (thread);
\r
836 + fputs (format->message_set_end, stdout);
\r
838 notmuch_query_destroy (query);
\r
839 notmuch_database_close (notmuch);
\r
841 diff --git a/notmuch.1 b/notmuch.1
\r
842 index 369ecba..e3d5acb 100644
\r
845 @@ -154,6 +154,12 @@ Supported options for
\r
849 +.BR \-\-format= ( json | text )
\r
851 +Presents the results in either JSON or plain-text (default).
\r
855 .BR \-\-sort= ( newest\-first | oldest\-first )
\r
857 This option can be used to present results in either chronological order
\r
858 @@ -202,7 +208,14 @@ matched message will be displayed.
\r
862 -The output format is plain-text, with all text-content MIME parts
\r
864 +.B \-\-format=(json|text)
\r
870 +The default plain-text format has text-content MIME parts
\r
871 decoded. Various components in the output,
\r
872 .RB ( message ", " header ", " body ", " attachment ", and MIME " part ),
\r
873 will be delimited by easily-parsed markers. Each marker consists of a
\r
874 @@ -210,6 +223,18 @@ Control-L character (ASCII decimal 12), the name of the marker, and
\r
875 then either an opening or closing brace, ('{' or '}'), to either open
\r
876 or close the component.
\r
883 +Format output as Javascript Object Notation (JSON). JSON output always
\r
884 +includes all messages in a matching thread; in effect
\r
885 +.B \-\-format=json
\r
887 +.B \-\-entire\-thread
\r
892 is to display a single thread of email messages. For this, use a
\r
893 diff --git a/notmuch.c b/notmuch.c
\r
894 index 2ac8a59..eaa6308 100644
\r
897 @@ -162,6 +162,11 @@ command_t commands[] = {
\r
899 "\t\tSupported options for search include:\n"
\r
901 + "\t\t--format=(json|text)\n"
\r
903 + "\t\t\tPresents the results in either JSON or\n"
\r
904 + "\t\t\tplain-text (default)\n"
\r
906 "\t\t--sort=(newest-first|oldest-first)\n"
\r
908 "\t\t\tPresent results in either chronological order\n"
\r
909 @@ -186,13 +191,23 @@ command_t commands[] = {
\r
910 "\t\t\tall messages in the same thread as any matched\n"
\r
911 "\t\t\tmessage will be displayed.\n"
\r
913 - "\t\tThe output format is plain-text, with all text-content\n"
\r
914 - "\t\tMIME parts decoded. Various components in the output,\n"
\r
915 - "\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n"
\r
916 - "\t\tare delimited by easily-parsed markers. Each marker consists\n"
\r
917 - "\t\tof a Control-L character (ASCII decimal 12), the name of\n"
\r
918 - "\t\tthe marker, and then either an opening or closing brace,\n"
\r
919 - "\t\t'{' or '}' to either open or close the component.\n"
\r
920 + "\t\t--format=(json|text)\n"
\r
922 + "\t\t\ttext\t(default)\n"
\r
924 + "\t\t\tThe plain-text has all text-content MIME parts decoded.\n"
\r
925 + "\t\t\tVarious components in the output, ('message', 'header',\n"
\r
926 + "\t\t\t'body', 'attachment', and MIME 'part') are delimited by\n"
\r
927 + "\t\t\teasily-parsed markers. Each marker consists of a Control-L\n"
\r
928 + "\t\t\tcharacter (ASCII decimal 12), the name of the marker, and\n"
\r
929 + "\t\t\tthen either an opening or closing brace, '{' or '}' to\n"
\r
930 + "\t\t\teither open or close the component.\n"
\r
934 + "\t\t\tFormat output as Javascript Object Notation (JSON).\n"
\r
935 + "\t\t\tJSON output always includes all messages in a matching,\n"
\r
936 + "\t\t\tthread i.e. '--output=json' implies '--entire-thread'\n"
\r
938 "\t\tA common use of \"notmuch show\" is to display a single\n"
\r
939 "\t\tthread of email messages. For this, use a search term of\n"
\r
940 diff --git a/show-message.c b/show-message.c
\r
941 index 784981b..05ced9c 100644
\r
942 --- a/show-message.c
\r
943 +++ b/show-message.c
\r
944 @@ -26,8 +26,6 @@ static void
\r
945 show_message_part (GMimeObject *part, int *part_count,
\r
946 void (*show_part) (GMimeObject *part, int *part_count))
\r
948 - *part_count = *part_count + 1;
\r
950 if (GMIME_IS_MULTIPART (part)) {
\r
951 GMimeMultipart *multipart = GMIME_MULTIPART (part);
\r
953 @@ -56,6 +54,8 @@ show_message_part (GMimeObject *part, int *part_count,
\r
957 + *part_count = *part_count + 1;
\r
959 (*show_part) (part, part_count);
\r