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 6449F431FBF
\r
6 for <notmuch@notmuchmail.org>; Fri, 18 Dec 2009 05:00:18 -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 wpoolJXwoZYh for <notmuch@notmuchmail.org>;
\r
11 Fri, 18 Dec 2009 05:00:16 -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 5493D431FAE
\r
14 for <notmuch@notmuchmail.org>; Fri, 18 Dec 2009 05:00:16 -0800 (PST)
\r
16 fctnnbsc30w-142167182194.pppoe-dynamic.high-speed.nb.bellaliant.net
\r
17 ([142.167.182.194] helo=localhost)
\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 1NLcRK-0006j6-OQ; Fri, 18 Dec 2009 09:00:15 -0400
\r
21 Received: from bremner by localhost with local (Exim 4.69)
\r
22 (envelope-from <bremner@pivot.cs.unb.ca>)
\r
23 id 1NLcRF-0001Qy-6t; Fri, 18 Dec 2009 09:00:09 -0400
\r
24 From: david@tethera.net
\r
25 To: notmuch@notmuchmail.org
\r
26 Date: Fri, 18 Dec 2009 08:59:55 -0400
\r
27 Message-Id: <1261141195-5469-1-git-send-email-david@tethera.net>
\r
28 X-Mailer: git-send-email 1.6.5.3
\r
29 In-Reply-To: <1261114167-sup-8228@lisa>
\r
30 References: <1261114167-sup-8228@lisa>
\r
31 X-Sender-Verified: bremner@pivot.cs.unb.ca
\r
32 Subject: [notmuch] [PATCH] Add an "--output=(json|text|)" command-line
\r
33 option 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: Fri, 18 Dec 2009 13:00:18 -0000
\r
48 From: Scott Robinson <scott@quadhome.com>
\r
50 In the case of notmuch-show, "--output=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
59 Edited-By: David Bremner <david@tethera.net>
\r
60 Reviewed-By: David Bremner <david@tethera.net>
\r
63 It took me a little work to apply Scott's patch, so rather than asking
\r
64 him to resend it from git-send-email, I am just sending. I hope no-one
\r
67 Other than manually extracting the patch from the output of notmuch
\r
68 show (for me the message arrived base64 encoded), I deleted trailing
\r
69 whitespace on line 465.
\r
71 It compiles, it doesn't seem to screw up the original output, and at
\r
72 least in a few tests, it generates parseable json. Yay!.
\r
74 I'm thinking that the patch I sent out last night to only dump message
\r
75 ids could be reworked to use the framework of this patch. I also
\r
76 think it would be reasonably simple to add an --output=mbox option,
\r
77 for archiving and so on.
\r
79 Makefile.local | 3 +-
\r
80 json.c | 73 ++++++++++++++
\r
81 notmuch-client.h | 3 +
\r
82 notmuch-search.c | 163 +++++++++++++++++++++++++++++---
\r
83 notmuch-show.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++--------
\r
84 notmuch.c | 24 ++++--
\r
85 show-message.c | 4 +-
\r
86 7 files changed, 481 insertions(+), 64 deletions(-)
\r
87 create mode 100644 json.c
\r
89 diff --git a/Makefile.local b/Makefile.local
\r
90 index 933ff4c..53b474b 100644
\r
91 --- a/Makefile.local
\r
92 +++ b/Makefile.local
\r
93 @@ -18,7 +18,8 @@ notmuch_client_srcs = \
\r
101 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
\r
102 notmuch: $(notmuch_client_modules) lib/notmuch.a
\r
103 diff --git a/json.c b/json.c
\r
104 new file mode 100644
\r
105 index 0000000..ee563d6
\r
109 +/* notmuch - Not much of an email program, (just index and search)
\r
111 + * Copyright © 2009 Carl Worth
\r
112 + * Copyright © 2009 Keith Packard
\r
114 + * This program is free software: you can redistribute it and/or modify
\r
115 + * it under the terms of the GNU General Public License as published by
\r
116 + * the Free Software Foundation, either version 3 of the License, or
\r
117 + * (at your option) any later version.
\r
119 + * This program is distributed in the hope that it will be useful,
\r
120 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
121 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
122 + * GNU General Public License for more details.
\r
124 + * You should have received a copy of the GNU General Public License
\r
125 + * along with this program. If not, see http://www.gnu.org/licenses/ .
\r
127 + * Authors: Carl Worth <cworth@cworth.org>
\r
128 + * Keith Packard <keithp@keithp.com>
\r
131 +#include "notmuch-client.h"
\r
134 + * json_quote_str derived from cJSON's print_string_ptr,
\r
135 + * Copyright (c) 2009 Dave Gamble
\r
139 +json_quote_str(const void *ctx, const char *str)
\r
149 + for (ptr = str; *ptr; len++, ptr++) {
\r
150 + if (*ptr < 32 || *ptr == '\"' || *ptr == '\\')
\r
154 + out = talloc_array (ctx, char, len + 3);
\r
161 + if (*ptr > 31 && *ptr != '\"' && *ptr != '\\') {
\r
162 + *ptr2++ = *ptr++;
\r
165 + switch (*ptr++) {
\r
166 + case '\"': *ptr2++ = '\"'; break;
\r
167 + case '\\': *ptr2++ = '\\'; break;
\r
168 + case '\b': *ptr2++ = 'b'; break;
\r
169 + case '\f': *ptr2++ = 'f'; break;
\r
170 + case '\n': *ptr2++ = 'n'; break;
\r
171 + case '\r': *ptr2++ = 'r'; break;
\r
172 + case '\t': *ptr2++ = 't'; break;
\r
173 + default: ptr2--; break;
\r
182 diff --git a/notmuch-client.h b/notmuch-client.h
\r
183 index 50a30fe..7b844b9 100644
\r
184 --- a/notmuch-client.h
\r
185 +++ b/notmuch-client.h
\r
186 @@ -143,6 +143,9 @@ notmuch_status_t
\r
187 show_message_body (const char *filename,
\r
188 void (*show_part) (GMimeObject *part, int *part_count));
\r
191 +json_quote_str (const void *ctx, const char *str);
\r
193 /* notmuch-config.c */
\r
195 typedef struct _notmuch_config notmuch_config_t;
\r
196 diff --git a/notmuch-search.c b/notmuch-search.c
\r
197 index dc44eb6..e243747 100644
\r
198 --- a/notmuch-search.c
\r
199 +++ b/notmuch-search.c
\r
200 @@ -20,8 +20,120 @@
\r
202 #include "notmuch-client.h"
\r
204 +typedef struct search_format {
\r
205 + const char *results_start;
\r
206 + const char *thread_start;
\r
207 + void (*thread) (const void *ctx,
\r
209 + const time_t date,
\r
210 + const int matched,
\r
212 + const char *authors,
\r
213 + const char *subject);
\r
214 + const char *tag_start;
\r
216 + const char *tag_sep;
\r
217 + const char *tag_end;
\r
218 + const char *thread_sep;
\r
219 + const char *thread_end;
\r
220 + const char *results_end;
\r
221 +} search_format_t;
\r
224 +format_thread_text (const void *ctx,
\r
226 + const time_t date,
\r
227 + const int matched,
\r
229 + const char *authors,
\r
230 + const char *subject);
\r
231 +static const search_format_t format_text = {
\r
234 + format_thread_text,
\r
243 +format_thread_json (const void *ctx,
\r
245 + const time_t date,
\r
246 + const int matched,
\r
248 + const char *authors,
\r
249 + const char *subject);
\r
250 +static const search_format_t format_json = {
\r
253 + format_thread_json,
\r
262 +format_thread_text (const void *ctx,
\r
264 + const time_t date,
\r
265 + const int matched,
\r
267 + const char *authors,
\r
268 + const char *subject)
\r
270 + printf ("thread:%s %12s [%d/%d] %s; %s",
\r
272 + notmuch_time_relative_date (ctx, date),
\r
280 +format_thread_json (const void *ctx,
\r
282 + const time_t date,
\r
283 + const int matched,
\r
285 + const char *authors,
\r
286 + const char *subject)
\r
289 + char timestamp[40];
\r
290 + void *ctx_quote = talloc_new (ctx);
\r
292 + tm = gmtime (&date);
\r
294 + INTERNAL_ERROR ("gmtime failed on thread %s.", id);
\r
296 + if (strftime (timestamp, sizeof (timestamp), "%s", tm) == 0)
\r
297 + INTERNAL_ERROR ("strftime failed on thread %s.", id);
\r
299 + printf ("\"id\": %s,\n"
\r
300 + "\"timestamp\": %s,\n"
\r
301 + "\"matched\": %d,\n"
\r
302 + "\"total\": %d,\n"
\r
303 + "\"authors\": %s,\n"
\r
304 + "\"subject\": %s,\n",
\r
305 + json_quote_str (ctx_quote, id),
\r
309 + json_quote_str (ctx_quote, authors),
\r
310 + json_quote_str (ctx_quote, subject));
\r
312 + talloc_free (ctx_quote);
\r
316 do_search_threads (const void *ctx,
\r
317 + const search_format_t *format,
\r
318 notmuch_query_t *query,
\r
319 notmuch_sort_t sort)
\r
321 @@ -29,7 +141,9 @@ do_search_threads (const void *ctx,
\r
322 notmuch_threads_t *threads;
\r
323 notmuch_tags_t *tags;
\r
325 - const char *relative_date;
\r
326 + int first_thread = 1;
\r
328 + fputs (format->results_start, stdout);
\r
330 for (threads = notmuch_query_search_threads (query);
\r
331 notmuch_threads_has_more (threads);
\r
332 @@ -37,6 +151,9 @@ do_search_threads (const void *ctx,
\r
336 + if (! first_thread)
\r
337 + fputs (format->thread_sep, stdout);
\r
339 thread = notmuch_threads_get (threads);
\r
341 if (sort == NOTMUCH_SORT_OLDEST_FIRST)
\r
342 @@ -44,30 +161,37 @@ do_search_threads (const void *ctx,
\r
344 date = notmuch_thread_get_newest_date (thread);
\r
346 - relative_date = notmuch_time_relative_date (ctx, date);
\r
347 + fputs (format->thread_start, stdout);
\r
349 + format->thread (ctx,
\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
357 - printf ("thread:%s %12s [%d/%d] %s; %s",
\r
358 - notmuch_thread_get_thread_id (thread),
\r
360 - notmuch_thread_get_matched_messages (thread),
\r
361 - notmuch_thread_get_total_messages (thread),
\r
362 - notmuch_thread_get_authors (thread),
\r
363 - notmuch_thread_get_subject (thread));
\r
364 + fputs (format->tag_start, stdout);
\r
367 for (tags = notmuch_thread_get_tags (thread);
\r
368 notmuch_tags_has_more (tags);
\r
369 notmuch_tags_advance (tags))
\r
373 - printf ("%s", notmuch_tags_get (tags));
\r
374 + fputs (format->tag_sep, stdout);
\r
375 + printf (format->tag, notmuch_tags_get (tags));
\r
380 + fputs (format->tag_end, stdout);
\r
381 + fputs (format->thread_end, stdout);
\r
383 + first_thread = 0;
\r
385 notmuch_thread_destroy (thread);
\r
388 + fputs (format->results_end, stdout);
\r
392 @@ -79,6 +203,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
395 notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
\r
396 + const search_format_t *format = &format_text;
\r
399 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
400 @@ -96,6 +221,16 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
401 fprintf (stderr, "Invalid value for --sort: %s\n", opt);
\r
404 + } else if (STRNCMP_LITERAL (argv[i], "--output=") == 0) {
\r
405 + opt = argv[i] + sizeof ("--output=") - 1;
\r
406 + if (strcmp (opt, "text") == 0) {
\r
407 + format = &format_text;
\r
408 + } else if (strcmp (opt, "json") == 0) {
\r
409 + format = &format_json;
\r
411 + fprintf (stderr, "Invalid value for --output: %s\n", opt);
\r
415 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
417 @@ -132,7 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
419 notmuch_query_set_sort (query, sort);
\r
421 - do_search_threads (ctx, query, sort);
\r
422 + do_search_threads (ctx, format, query, sort);
\r
424 notmuch_query_destroy (query);
\r
425 notmuch_database_close (notmuch);
\r
426 diff --git a/notmuch-show.c b/notmuch-show.c
\r
427 index 376aacd..b5b3eba 100644
\r
428 --- a/notmuch-show.c
\r
429 +++ b/notmuch-show.c
\r
432 #include "notmuch-client.h"
\r
434 +typedef struct show_format {
\r
435 + const char *message_set_start;
\r
436 + const char *message_start;
\r
437 + void (*message) (const void *ctx,
\r
438 + notmuch_message_t *message,
\r
440 + const char *header_start;
\r
441 + void (*header) (const void *ctx,
\r
442 + notmuch_message_t *message);
\r
443 + const char *header_end;
\r
444 + const char *body_start;
\r
445 + void (*part) (GMimeObject *part,
\r
446 + int *part_count);
\r
447 + const char *body_end;
\r
448 + const char *message_end;
\r
449 + const char *message_set_sep;
\r
450 + const char *message_set_end;
\r
454 +format_message_text (unused (const void *ctx),
\r
455 + notmuch_message_t *message,
\r
458 +format_headers_text (const void *ctx,
\r
459 + notmuch_message_t *message);
\r
461 +format_part_text (GMimeObject *part,
\r
462 + int *part_count);
\r
463 +static const show_format_t format_text = {
\r
465 + "\fmessage{ ", format_message_text,
\r
466 + "\fheader{\n", format_headers_text, "\fheader}\n",
\r
467 + "\fbody{\n", format_part_text, "\fbody}\n",
\r
468 + "\fmessage}\n", "",
\r
473 +format_message_json (const void *ctx,
\r
474 + notmuch_message_t *message,
\r
475 + unused (int indent));
\r
477 +format_headers_json (const void *ctx,
\r
478 + notmuch_message_t *message);
\r
480 +format_part_json (GMimeObject *part,
\r
481 + int *part_count);
\r
482 +static const show_format_t format_json = {
\r
484 + "{", format_message_json,
\r
485 + ", \"headers\": {", format_headers_json, "}",
\r
486 + ", \"body\": [", format_part_json, "]",
\r
491 static const char *
\r
492 -_get_tags_as_string (void *ctx, notmuch_message_t *message)
\r
493 +_get_tags_as_string (const void *ctx, notmuch_message_t *message)
\r
495 notmuch_tags_t *tags;
\r
497 @@ -48,7 +105,7 @@ _get_tags_as_string (void *ctx, notmuch_message_t *message)
\r
499 /* Get a nice, single-line summary of message. */
\r
500 static const char *
\r
501 -_get_one_line_summary (void *ctx, notmuch_message_t *message)
\r
502 +_get_one_line_summary (const void *ctx, notmuch_message_t *message)
\r
506 @@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)
\r
510 -show_part_content (GMimeObject *part)
\r
511 +format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)
\r
513 + printf ("id:%s depth:%d match:%d filename:%s\n",
\r
514 + notmuch_message_get_message_id (message),
\r
516 + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
\r
517 + notmuch_message_get_filename (message));
\r
521 +format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))
\r
523 + void *ctx_quote = talloc_new (ctx);
\r
525 + printf ("\"id\": %s, \"match\": %s, \"filename\": %s",
\r
526 + json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
\r
527 + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
\r
528 + json_quote_str (ctx_quote, notmuch_message_get_filename (message)));
\r
530 + talloc_free (ctx_quote);
\r
534 +format_headers_text (const void *ctx, notmuch_message_t *message)
\r
536 + const char *headers[] = {
\r
537 + "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
539 + const char *name, *value;
\r
542 + printf ("%s\n", _get_one_line_summary (ctx, message));
\r
544 + for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
545 + name = headers[i];
\r
546 + value = notmuch_message_get_header (message, name);
\r
548 + printf ("%s: %s\n", name, value);
\r
553 +format_headers_json (const void *ctx, notmuch_message_t *message)
\r
555 + const char *headers[] = {
\r
556 + "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
558 + const char *name, *value;
\r
560 + int first_header = 1;
\r
561 + void *ctx_quote = talloc_new (ctx);
\r
563 + for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
564 + name = headers[i];
\r
565 + value = notmuch_message_get_header (message, name);
\r
568 + if (!first_header)
\r
569 + fputs (", ", stdout);
\r
570 + first_header = 0;
\r
572 + printf ("%s: %s",
\r
573 + json_quote_str (ctx_quote, name),
\r
574 + json_quote_str (ctx_quote, value));
\r
578 + talloc_free (ctx_quote);
\r
582 +show_part_content (GMimeObject *part, GMimeStream *stream_out)
\r
584 - GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
\r
585 GMimeStream *stream_filter = NULL;
\r
586 GMimeDataWrapper *wrapper;
\r
587 const char *charset;
\r
589 charset = g_mime_object_get_content_type_parameter (part, "charset");
\r
591 - if (stream_stdout) {
\r
592 - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
593 - stream_filter = g_mime_stream_filter_new(stream_stdout);
\r
594 + if (stream_out) {
\r
595 + stream_filter = g_mime_stream_filter_new(stream_out);
\r
596 g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
\r
597 g_mime_filter_crlf_new(FALSE, FALSE));
\r
599 @@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)
\r
600 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
\r
602 g_object_unref(stream_filter);
\r
603 - if (stream_stdout)
\r
604 - g_object_unref(stream_stdout);
\r
608 -show_part (GMimeObject *part, int *part_count)
\r
609 +format_part_text (GMimeObject *part, int *part_count)
\r
611 GMimeContentDisposition *disposition;
\r
612 GMimeContentType *content_type;
\r
613 + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
\r
615 + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
617 disposition = g_mime_object_get_content_disposition (part);
\r
619 @@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)
\r
620 if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
621 !g_mime_content_type_is_type (content_type, "text", "html"))
\r
623 - show_part_content (part);
\r
624 + show_part_content (part, stream_stdout);
\r
627 printf ("\fattachment}\n");
\r
629 + if (stream_stdout)
\r
630 + g_object_unref(stream_stdout);
\r
635 @@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)
\r
636 if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
637 !g_mime_content_type_is_type (content_type, "text", "html"))
\r
639 - show_part_content (part);
\r
640 + show_part_content (part, stream_stdout);
\r
644 @@ -144,57 +274,93 @@ show_part (GMimeObject *part, int *part_count)
\r
647 printf ("\fpart}\n");
\r
649 + if (stream_stdout)
\r
650 + g_object_unref(stream_stdout);
\r
654 -show_message (void *ctx, notmuch_message_t *message, int indent)
\r
655 +format_part_json (GMimeObject *part, int *part_count)
\r
657 - const char *headers[] = {
\r
658 - "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
660 - const char *name, *value;
\r
662 + GMimeContentType *content_type;
\r
663 + GMimeContentDisposition *disposition;
\r
664 + void *ctx = talloc_new (NULL);
\r
665 + GMimeStream *stream_memory = g_mime_stream_mem_new ();
\r
666 + GByteArray *part_content;
\r
668 - printf ("\fmessage{ id:%s depth:%d match:%d filename:%s\n",
\r
669 - notmuch_message_get_message_id (message),
\r
671 - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
\r
672 - notmuch_message_get_filename (message));
\r
673 + content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
\r
675 - printf ("\fheader{\n");
\r
676 + if (*part_count > 1)
\r
677 + fputs (", ", stdout);
\r
679 - printf ("%s\n", _get_one_line_summary (ctx, message));
\r
680 + printf ("{\"id\": %d, \"content-type\": %s",
\r
682 + json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
\r
684 - for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
685 - name = headers[i];
\r
686 - value = notmuch_message_get_header (message, name);
\r
688 - printf ("%s: %s\n", name, value);
\r
689 + disposition = g_mime_object_get_content_disposition (part);
\r
690 + if (disposition &&
\r
691 + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
\r
693 + const char *filename = g_mime_part_get_filename (GMIME_PART (part));
\r
695 + printf (", \"filename\": %s", json_quote_str (ctx, filename));
\r
698 - printf ("\fheader}\n");
\r
699 - printf ("\fbody{\n");
\r
700 + if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
701 + !g_mime_content_type_is_type (content_type, "text", "html"))
\r
703 + show_part_content (part, stream_memory);
\r
704 + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
\r
706 + printf (", \"content\": %s", json_quote_str (ctx, (char *) part_content->data));
\r
709 + fputs ("}", stdout);
\r
711 + talloc_free (ctx);
\r
712 + if (stream_memory)
\r
713 + g_object_unref (stream_memory);
\r
717 +show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)
\r
719 + fputs (format->message_start, stdout);
\r
720 + format->message(ctx, message, indent);
\r
722 - show_message_body (notmuch_message_get_filename (message), show_part);
\r
723 + fputs (format->header_start, stdout);
\r
724 + format->header(ctx, message);
\r
725 + fputs (format->header_end, stdout);
\r
727 - printf ("\fbody}\n");
\r
728 + fputs (format->body_start, stdout);
\r
729 + show_message_body (notmuch_message_get_filename (message), format->part);
\r
730 + fputs (format->body_end, stdout);
\r
732 - printf ("\fmessage}\n");
\r
733 + fputs (format->message_end, stdout);
\r
738 -show_messages (void *ctx, notmuch_messages_t *messages, int indent,
\r
739 +show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,
\r
740 notmuch_bool_t entire_thread)
\r
742 notmuch_message_t *message;
\r
743 notmuch_bool_t match;
\r
744 + int first_set = 1;
\r
747 + fputs (format->message_set_start, stdout);
\r
750 notmuch_messages_has_more (messages);
\r
751 notmuch_messages_advance (messages))
\r
754 + fputs (format->message_set_sep, stdout);
\r
757 + fputs (format->message_set_start, stdout);
\r
759 message = notmuch_messages_get (messages);
\r
761 match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
\r
762 @@ -202,15 +368,21 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent,
\r
763 next_indent = indent;
\r
765 if (match || entire_thread) {
\r
766 - show_message (ctx, message, indent);
\r
767 + show_message (ctx, format, message, indent);
\r
768 next_indent = indent + 1;
\r
770 + fputs (format->message_set_sep, stdout);
\r
773 - show_messages (ctx, notmuch_message_get_replies (message),
\r
774 + show_messages (ctx, format, notmuch_message_get_replies (message),
\r
775 next_indent, entire_thread);
\r
777 notmuch_message_destroy (message);
\r
779 + fputs (format->message_set_end, stdout);
\r
782 + fputs (format->message_set_end, stdout);
\r
786 @@ -223,15 +395,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
787 notmuch_thread_t *thread;
\r
788 notmuch_messages_t *messages;
\r
789 char *query_string;
\r
791 + const show_format_t *format = &format_text;
\r
792 int entire_thread = 0;
\r
794 + int first_toplevel = 1;
\r
796 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
797 if (strcmp (argv[i], "--") == 0) {
\r
801 - if (strcmp(argv[i], "--entire-thread") == 0) {
\r
802 + if (STRNCMP_LITERAL (argv[i], "--output=") == 0) {
\r
803 + opt = argv[i] + sizeof ("--output=") - 1;
\r
804 + if (strcmp (opt, "text") == 0) {
\r
805 + format = &format_text;
\r
806 + } else if (strcmp (opt, "json") == 0) {
\r
807 + format = &format_json;
\r
808 + entire_thread = 1;
\r
810 + fprintf (stderr, "Invalid value for --output: %s\n", opt);
\r
813 + } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
\r
816 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
817 @@ -268,6 +454,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
821 + fputs (format->message_set_start, stdout);
\r
823 for (threads = notmuch_query_search_threads (query);
\r
824 notmuch_threads_has_more (threads);
\r
825 notmuch_threads_advance (threads))
\r
826 @@ -280,11 +468,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
827 INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
\r
828 notmuch_thread_get_thread_id (thread));
\r
830 - show_messages (ctx, messages, 0, entire_thread);
\r
831 + if (!first_toplevel)
\r
832 + fputs (format->message_set_sep, stdout);
\r
833 + first_toplevel = 0;
\r
835 + show_messages (ctx, format, messages, 0, entire_thread);
\r
837 notmuch_thread_destroy (thread);
\r
841 + fputs (format->message_set_end, stdout);
\r
843 notmuch_query_destroy (query);
\r
844 notmuch_database_close (notmuch);
\r
846 diff --git a/notmuch.c b/notmuch.c
\r
847 index 2ac8a59..aa2fc12 100644
\r
850 @@ -162,6 +162,11 @@ command_t commands[] = {
\r
852 "\t\tSupported options for search include:\n"
\r
854 + "\t\t--output=(json|text)\n"
\r
856 + "\t\t\tPresents the results in either JSON or plain-text\n"
\r
857 + "\t\t\tformat, which is the default.\n"
\r
859 "\t\t--sort=(newest-first|oldest-first)\n"
\r
861 "\t\t\tPresent results in either chronological order\n"
\r
862 @@ -186,13 +191,18 @@ command_t commands[] = {
\r
863 "\t\t\tall messages in the same thread as any matched\n"
\r
864 "\t\t\tmessage will be displayed.\n"
\r
866 - "\t\tThe output format is plain-text, with all text-content\n"
\r
867 - "\t\tMIME parts decoded. Various components in the output,\n"
\r
868 - "\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n"
\r
869 - "\t\tare delimited by easily-parsed markers. Each marker consists\n"
\r
870 - "\t\tof a Control-L character (ASCII decimal 12), the name of\n"
\r
871 - "\t\tthe marker, and then either an opening or closing brace,\n"
\r
872 - "\t\t'{' or '}' to either open or close the component.\n"
\r
873 + "\t\t--output=(json|text)\n"
\r
875 + "\t\t\tPresents the results in either JSON or plain-text\n"
\r
876 + "\t\t\tformat, which is the default.\n"
\r
878 + "\t\tThe plain-text has all text-content MIME parts decoded.\n"
\r
879 + "\t\tVarious components in the output, ('message', 'header',\n"
\r
880 + "\t\t'body', 'attachment', and MIME 'part') are delimited by\n"
\r
881 + "\t\teasily-parsed markers. Each marker consists of a Control-L\n"
\r
882 + "\t\tcharacter (ASCII decimal 12), the name of the marker, and\n"
\r
883 + "\t\tthen either an opening or closing brace, '{' or '}' to\n"
\r
884 + "\t\teither open or close the component.\n"
\r
886 "\t\tA common use of \"notmuch show\" is to display a single\n"
\r
887 "\t\tthread of email messages. For this, use a search term of\n"
\r
888 diff --git a/show-message.c b/show-message.c
\r
889 index 784981b..05ced9c 100644
\r
890 --- a/show-message.c
\r
891 +++ b/show-message.c
\r
892 @@ -26,8 +26,6 @@ static void
\r
893 show_message_part (GMimeObject *part, int *part_count,
\r
894 void (*show_part) (GMimeObject *part, int *part_count))
\r
896 - *part_count = *part_count + 1;
\r
898 if (GMIME_IS_MULTIPART (part)) {
\r
899 GMimeMultipart *multipart = GMIME_MULTIPART (part);
\r
901 @@ -56,6 +54,8 @@ show_message_part (GMimeObject *part, int *part_count,
\r
905 + *part_count = *part_count + 1;
\r
907 (*show_part) (part, part_count);
\r