1 Return-Path: <scott@quadhome.com>
\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 059C2431FBF
\r
6 for <notmuch@notmuchmail.org>; Thu, 17 Dec 2009 21:34:01 -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 uQPCro26a3Bq for <notmuch@notmuchmail.org>;
\r
11 Thu, 17 Dec 2009 21:33:58 -0800 (PST)
\r
12 Received: from megan.quadhome.com (megan.quadhome.com [209.9.237.126])
\r
13 by olra.theworths.org (Postfix) with ESMTP id 9B09A431FAE
\r
14 for <notmuch@notmuchmail.org>; Thu, 17 Dec 2009 21:33:58 -0800 (PST)
\r
15 Received: from elise.quadhome.com (c-24-22-29-24.hsd1.or.comcast.net
\r
17 by megan.quadhome.com (Postfix) with ESMTPSA id AE503B8068
\r
18 for <notmuch@notmuchmail.org>; Thu, 17 Dec 2009 21:33:56 -0800 (PST)
\r
19 Content-Type: text/plain; charset=UTF-8
\r
20 From: Scott Robinson <scott@quadhome.com>
\r
21 To: notmuch <notmuch@notmuchmail.org>
\r
22 In-reply-to: <87skbdcfu3.fsf@pivot.cs.unb.ca>
\r
23 References: <87iqcapijl.fsf@pivot.cs.unb.ca>
\r
24 <87k4wp2noq.fsf@yoom.home.cworth.org>
\r
25 <87skbdcfu3.fsf@pivot.cs.unb.ca>
\r
26 Date: Thu, 17 Dec 2009 21:33:54 -0800
\r
27 Message-Id: <1261114167-sup-8228@lisa>
\r
29 Content-Transfer-Encoding: 8bit
\r
30 Subject: [notmuch] [PATCH] JSON output for notmuch-search and notmuch-show.
\r
31 X-BeenThere: notmuch@notmuchmail.org
\r
32 X-Mailman-Version: 2.1.12
\r
34 List-Id: "Use and development of the notmuch mail system."
\r
35 <notmuch.notmuchmail.org>
\r
36 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
37 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
38 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
39 List-Post: <mailto:notmuch@notmuchmail.org>
\r
40 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
41 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
42 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
43 X-List-Received-Date: Fri, 18 Dec 2009 05:34:01 -0000
\r
45 Excerpts from David Bremner's message of Mon Dec 14 15:10:12 -0800 2009:
\r
46 > OK, I'll wait for Scott's patch, and see how he is using cJSON. Just
\r
47 > stealing a few functions might be the way to go.
\r
49 > The cJSON code is less horrifying after running through indent. Now all
\r
50 > we need is "indent --carl" :)
\r
53 I took an earlier suggestion and didn't use cJSON, instead writing custom code
\r
54 for emitting the new format.
\r
58 Added an "--output=(json|text|)" command-line option to both
\r
59 notmuch-search and notmuch-show.
\r
61 In the case of notmuch-show, "--output=json" also implies
\r
62 "--entire-thread" as the thread structure is implicit in the emitted
\r
65 As a coincidence to the implementation, multipart message ID numbers are
\r
66 now incremented with each part printed. This changes the previous
\r
67 semantics, which were unclear and not necessary related to the actual
\r
68 ordering of the message parts.
\r
70 Makefile.local | 3 +-
\r
71 json.c | 73 ++++++++++++++
\r
72 notmuch-client.h | 3 +
\r
73 notmuch-search.c | 163 +++++++++++++++++++++++++++++---
\r
74 notmuch-show.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++--------
\r
75 notmuch.c | 24 ++++--
\r
76 show-message.c | 4 +-
\r
77 7 files changed, 481 insertions(+), 64 deletions(-)
\r
78 create mode 100644 json.c
\r
80 diff --git a/Makefile.local b/Makefile.local
\r
81 index 933ff4c..53b474b 100644
\r
82 --- a/Makefile.local
\r
83 +++ b/Makefile.local
\r
84 @@ -18,7 +18,8 @@ notmuch_client_srcs = \
\r
92 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
\r
93 notmuch: $(notmuch_client_modules) lib/notmuch.a
\r
94 diff --git a/json.c b/json.c
\r
95 new file mode 100644
\r
96 index 0000000..ee563d6
\r
100 +/* notmuch - Not much of an email program, (just index and search)
\r
102 + * Copyright © 2009 Carl Worth
\r
103 + * Copyright © 2009 Keith Packard
\r
105 + * This program is free software: you can redistribute it and/or modify
\r
106 + * it under the terms of the GNU General Public License as published by
\r
107 + * the Free Software Foundation, either version 3 of the License, or
\r
108 + * (at your option) any later version.
\r
110 + * This program is distributed in the hope that it will be useful,
\r
111 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
112 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
113 + * GNU General Public License for more details.
\r
115 + * You should have received a copy of the GNU General Public License
\r
116 + * along with this program. If not, see http://www.gnu.org/licenses/ .
\r
118 + * Authors: Carl Worth <cworth@cworth.org>
\r
119 + * Keith Packard <keithp@keithp.com>
\r
122 +#include "notmuch-client.h"
\r
125 + * json_quote_str derived from cJSON's print_string_ptr,
\r
126 + * Copyright (c) 2009 Dave Gamble
\r
130 +json_quote_str(const void *ctx, const char *str)
\r
140 + for (ptr = str; *ptr; len++, ptr++) {
\r
141 + if (*ptr < 32 || *ptr == '\"' || *ptr == '\\')
\r
145 + out = talloc_array (ctx, char, len + 3);
\r
152 + if (*ptr > 31 && *ptr != '\"' && *ptr != '\\') {
\r
153 + *ptr2++ = *ptr++;
\r
156 + switch (*ptr++) {
\r
157 + case '\"': *ptr2++ = '\"'; break;
\r
158 + case '\\': *ptr2++ = '\\'; break;
\r
159 + case '\b': *ptr2++ = 'b'; break;
\r
160 + case '\f': *ptr2++ = 'f'; break;
\r
161 + case '\n': *ptr2++ = 'n'; break;
\r
162 + case '\r': *ptr2++ = 'r'; break;
\r
163 + case '\t': *ptr2++ = 't'; break;
\r
164 + default: ptr2--; break;
\r
173 diff --git a/notmuch-client.h b/notmuch-client.h
\r
174 index 50a30fe..7b844b9 100644
\r
175 --- a/notmuch-client.h
\r
176 +++ b/notmuch-client.h
\r
177 @@ -143,6 +143,9 @@ notmuch_status_t
\r
178 show_message_body (const char *filename,
\r
179 void (*show_part) (GMimeObject *part, int *part_count));
\r
182 +json_quote_str (const void *ctx, const char *str);
\r
184 /* notmuch-config.c */
\r
186 typedef struct _notmuch_config notmuch_config_t;
\r
187 diff --git a/notmuch-search.c b/notmuch-search.c
\r
188 index dc44eb6..e243747 100644
\r
189 --- a/notmuch-search.c
\r
190 +++ b/notmuch-search.c
\r
191 @@ -20,8 +20,120 @@
\r
193 #include "notmuch-client.h"
\r
195 +typedef struct search_format {
\r
196 + const char *results_start;
\r
197 + const char *thread_start;
\r
198 + void (*thread) (const void *ctx,
\r
200 + const time_t date,
\r
201 + const int matched,
\r
203 + const char *authors,
\r
204 + const char *subject);
\r
205 + const char *tag_start;
\r
207 + const char *tag_sep;
\r
208 + const char *tag_end;
\r
209 + const char *thread_sep;
\r
210 + const char *thread_end;
\r
211 + const char *results_end;
\r
212 +} search_format_t;
\r
215 +format_thread_text (const void *ctx,
\r
217 + const time_t date,
\r
218 + const int matched,
\r
220 + const char *authors,
\r
221 + const char *subject);
\r
222 +static const search_format_t format_text = {
\r
225 + format_thread_text,
\r
234 +format_thread_json (const void *ctx,
\r
236 + const time_t date,
\r
237 + const int matched,
\r
239 + const char *authors,
\r
240 + const char *subject);
\r
241 +static const search_format_t format_json = {
\r
244 + format_thread_json,
\r
253 +format_thread_text (const void *ctx,
\r
255 + const time_t date,
\r
256 + const int matched,
\r
258 + const char *authors,
\r
259 + const char *subject)
\r
261 + printf ("thread:%s %12s [%d/%d] %s; %s",
\r
263 + notmuch_time_relative_date (ctx, date),
\r
271 +format_thread_json (const void *ctx,
\r
273 + const time_t date,
\r
274 + const int matched,
\r
276 + const char *authors,
\r
277 + const char *subject)
\r
280 + char timestamp[40];
\r
281 + void *ctx_quote = talloc_new (ctx);
\r
283 + tm = gmtime (&date);
\r
285 + INTERNAL_ERROR ("gmtime failed on thread %s.", id);
\r
287 + if (strftime (timestamp, sizeof (timestamp), "%s", tm) == 0)
\r
288 + INTERNAL_ERROR ("strftime failed on thread %s.", id);
\r
290 + printf ("\"id\": %s,\n"
\r
291 + "\"timestamp\": %s,\n"
\r
292 + "\"matched\": %d,\n"
\r
293 + "\"total\": %d,\n"
\r
294 + "\"authors\": %s,\n"
\r
295 + "\"subject\": %s,\n",
\r
296 + json_quote_str (ctx_quote, id),
\r
300 + json_quote_str (ctx_quote, authors),
\r
301 + json_quote_str (ctx_quote, subject));
\r
303 + talloc_free (ctx_quote);
\r
307 do_search_threads (const void *ctx,
\r
308 + const search_format_t *format,
\r
309 notmuch_query_t *query,
\r
310 notmuch_sort_t sort)
\r
312 @@ -29,7 +141,9 @@ do_search_threads (const void *ctx,
\r
313 notmuch_threads_t *threads;
\r
314 notmuch_tags_t *tags;
\r
316 - const char *relative_date;
\r
317 + int first_thread = 1;
\r
319 + fputs (format->results_start, stdout);
\r
321 for (threads = notmuch_query_search_threads (query);
\r
322 notmuch_threads_has_more (threads);
\r
323 @@ -37,6 +151,9 @@ do_search_threads (const void *ctx,
\r
327 + if (! first_thread)
\r
328 + fputs (format->thread_sep, stdout);
\r
330 thread = notmuch_threads_get (threads);
\r
332 if (sort == NOTMUCH_SORT_OLDEST_FIRST)
\r
333 @@ -44,30 +161,37 @@ do_search_threads (const void *ctx,
\r
335 date = notmuch_thread_get_newest_date (thread);
\r
337 - relative_date = notmuch_time_relative_date (ctx, date);
\r
338 + fputs (format->thread_start, stdout);
\r
340 + format->thread (ctx,
\r
341 + notmuch_thread_get_thread_id (thread),
\r
343 + notmuch_thread_get_matched_messages (thread),
\r
344 + notmuch_thread_get_total_messages (thread),
\r
345 + notmuch_thread_get_authors (thread),
\r
346 + notmuch_thread_get_subject (thread));
\r
348 - printf ("thread:%s %12s [%d/%d] %s; %s",
\r
349 - notmuch_thread_get_thread_id (thread),
\r
351 - notmuch_thread_get_matched_messages (thread),
\r
352 - notmuch_thread_get_total_messages (thread),
\r
353 - notmuch_thread_get_authors (thread),
\r
354 - notmuch_thread_get_subject (thread));
\r
355 + fputs (format->tag_start, stdout);
\r
358 for (tags = notmuch_thread_get_tags (thread);
\r
359 notmuch_tags_has_more (tags);
\r
360 notmuch_tags_advance (tags))
\r
364 - printf ("%s", notmuch_tags_get (tags));
\r
365 + fputs (format->tag_sep, stdout);
\r
366 + printf (format->tag, notmuch_tags_get (tags));
\r
371 + fputs (format->tag_end, stdout);
\r
372 + fputs (format->thread_end, stdout);
\r
374 + first_thread = 0;
\r
376 notmuch_thread_destroy (thread);
\r
379 + fputs (format->results_end, stdout);
\r
383 @@ -79,6 +203,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
386 notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
\r
387 + const search_format_t *format = &format_text;
\r
390 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
391 @@ -96,6 +221,16 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
392 fprintf (stderr, "Invalid value for --sort: %s\n", opt);
\r
395 + } else if (STRNCMP_LITERAL (argv[i], "--output=") == 0) {
\r
396 + opt = argv[i] + sizeof ("--output=") - 1;
\r
397 + if (strcmp (opt, "text") == 0) {
\r
398 + format = &format_text;
\r
399 + } else if (strcmp (opt, "json") == 0) {
\r
400 + format = &format_json;
\r
402 + fprintf (stderr, "Invalid value for --output: %s\n", opt);
\r
406 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
408 @@ -132,7 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
410 notmuch_query_set_sort (query, sort);
\r
412 - do_search_threads (ctx, query, sort);
\r
413 + do_search_threads (ctx, format, query, sort);
\r
415 notmuch_query_destroy (query);
\r
416 notmuch_database_close (notmuch);
\r
417 diff --git a/notmuch-show.c b/notmuch-show.c
\r
418 index 376aacd..70d2605 100644
\r
419 --- a/notmuch-show.c
\r
420 +++ b/notmuch-show.c
\r
423 #include "notmuch-client.h"
\r
425 +typedef struct show_format {
\r
426 + const char *message_set_start;
\r
427 + const char *message_start;
\r
428 + void (*message) (const void *ctx,
\r
429 + notmuch_message_t *message,
\r
431 + const char *header_start;
\r
432 + void (*header) (const void *ctx,
\r
433 + notmuch_message_t *message);
\r
434 + const char *header_end;
\r
435 + const char *body_start;
\r
436 + void (*part) (GMimeObject *part,
\r
437 + int *part_count);
\r
438 + const char *body_end;
\r
439 + const char *message_end;
\r
440 + const char *message_set_sep;
\r
441 + const char *message_set_end;
\r
445 +format_message_text (unused (const void *ctx),
\r
446 + notmuch_message_t *message,
\r
449 +format_headers_text (const void *ctx,
\r
450 + notmuch_message_t *message);
\r
452 +format_part_text (GMimeObject *part,
\r
453 + int *part_count);
\r
454 +static const show_format_t format_text = {
\r
456 + "\fmessage{ ", format_message_text,
\r
457 + "\fheader{\n", format_headers_text, "\fheader}\n",
\r
458 + "\fbody{\n", format_part_text, "\fbody}\n",
\r
459 + "\fmessage}\n", "",
\r
464 +format_message_json (const void *ctx,
\r
465 + notmuch_message_t *message,
\r
466 + unused (int indent));
\r
468 +format_headers_json (const void *ctx,
\r
469 + notmuch_message_t *message);
\r
471 +format_part_json (GMimeObject *part,
\r
472 + int *part_count);
\r
473 +static const show_format_t format_json = {
\r
475 + "{", format_message_json,
\r
476 + ", \"headers\": {", format_headers_json, "}",
\r
477 + ", \"body\": [", format_part_json, "]",
\r
482 static const char *
\r
483 -_get_tags_as_string (void *ctx, notmuch_message_t *message)
\r
484 +_get_tags_as_string (const void *ctx, notmuch_message_t *message)
\r
486 notmuch_tags_t *tags;
\r
488 @@ -48,7 +105,7 @@ _get_tags_as_string (void *ctx, notmuch_message_t *message)
\r
490 /* Get a nice, single-line summary of message. */
\r
491 static const char *
\r
492 -_get_one_line_summary (void *ctx, notmuch_message_t *message)
\r
493 +_get_one_line_summary (const void *ctx, notmuch_message_t *message)
\r
497 @@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)
\r
501 -show_part_content (GMimeObject *part)
\r
502 +format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)
\r
504 + printf ("id:%s depth:%d match:%d filename:%s\n",
\r
505 + notmuch_message_get_message_id (message),
\r
507 + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
\r
508 + notmuch_message_get_filename (message));
\r
512 +format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))
\r
514 + void *ctx_quote = talloc_new (ctx);
\r
516 + printf ("\"id\": %s, \"match\": %s, \"filename\": %s",
\r
517 + json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
\r
518 + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
\r
519 + json_quote_str (ctx_quote, notmuch_message_get_filename (message)));
\r
521 + talloc_free (ctx_quote);
\r
525 +format_headers_text (const void *ctx, notmuch_message_t *message)
\r
527 + const char *headers[] = {
\r
528 + "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
530 + const char *name, *value;
\r
533 + printf ("%s\n", _get_one_line_summary (ctx, message));
\r
535 + for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
536 + name = headers[i];
\r
537 + value = notmuch_message_get_header (message, name);
\r
539 + printf ("%s: %s\n", name, value);
\r
544 +format_headers_json (const void *ctx, notmuch_message_t *message)
\r
546 + const char *headers[] = {
\r
547 + "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
549 + const char *name, *value;
\r
551 + int first_header = 1;
\r
552 + void *ctx_quote = talloc_new (ctx);
\r
554 + for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
555 + name = headers[i];
\r
556 + value = notmuch_message_get_header (message, name);
\r
559 + if (!first_header)
\r
560 + fputs (", ", stdout);
\r
561 + first_header = 0;
\r
563 + printf ("%s: %s",
\r
564 + json_quote_str (ctx_quote, name),
\r
565 + json_quote_str (ctx_quote, value));
\r
569 + talloc_free (ctx_quote);
\r
573 +show_part_content (GMimeObject *part, GMimeStream *stream_out)
\r
575 - GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
\r
576 GMimeStream *stream_filter = NULL;
\r
577 GMimeDataWrapper *wrapper;
\r
578 const char *charset;
\r
580 charset = g_mime_object_get_content_type_parameter (part, "charset");
\r
582 - if (stream_stdout) {
\r
583 - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
584 - stream_filter = g_mime_stream_filter_new(stream_stdout);
\r
585 + if (stream_out) {
\r
586 + stream_filter = g_mime_stream_filter_new(stream_out);
\r
587 g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
\r
588 g_mime_filter_crlf_new(FALSE, FALSE));
\r
590 @@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)
\r
591 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
\r
593 g_object_unref(stream_filter);
\r
594 - if (stream_stdout)
\r
595 - g_object_unref(stream_stdout);
\r
599 -show_part (GMimeObject *part, int *part_count)
\r
600 +format_part_text (GMimeObject *part, int *part_count)
\r
602 GMimeContentDisposition *disposition;
\r
603 GMimeContentType *content_type;
\r
604 + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
\r
606 + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
\r
608 disposition = g_mime_object_get_content_disposition (part);
\r
610 @@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)
\r
611 if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
612 !g_mime_content_type_is_type (content_type, "text", "html"))
\r
614 - show_part_content (part);
\r
615 + show_part_content (part, stream_stdout);
\r
618 printf ("\fattachment}\n");
\r
620 + if (stream_stdout)
\r
621 + g_object_unref(stream_stdout);
\r
626 @@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)
\r
627 if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
628 !g_mime_content_type_is_type (content_type, "text", "html"))
\r
630 - show_part_content (part);
\r
631 + show_part_content (part, stream_stdout);
\r
635 @@ -144,57 +274,93 @@ show_part (GMimeObject *part, int *part_count)
\r
638 printf ("\fpart}\n");
\r
640 + if (stream_stdout)
\r
641 + g_object_unref(stream_stdout);
\r
645 -show_message (void *ctx, notmuch_message_t *message, int indent)
\r
646 +format_part_json (GMimeObject *part, int *part_count)
\r
648 - const char *headers[] = {
\r
649 - "Subject", "From", "To", "Cc", "Bcc", "Date"
\r
651 - const char *name, *value;
\r
653 + GMimeContentType *content_type;
\r
654 + GMimeContentDisposition *disposition;
\r
655 + void *ctx = talloc_new (NULL);
\r
656 + GMimeStream *stream_memory = g_mime_stream_mem_new ();
\r
657 + GByteArray *part_content;
\r
659 - printf ("\fmessage{ id:%s depth:%d match:%d filename:%s\n",
\r
660 - notmuch_message_get_message_id (message),
\r
662 - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
\r
663 - notmuch_message_get_filename (message));
\r
664 + content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
\r
666 - printf ("\fheader{\n");
\r
667 + if (*part_count > 1)
\r
668 + fputs (", ", stdout);
\r
670 - printf ("%s\n", _get_one_line_summary (ctx, message));
\r
671 + printf ("{\"id\": %d, \"content-type\": %s",
\r
673 + json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
\r
675 - for (i = 0; i < ARRAY_SIZE (headers); i++) {
\r
676 - name = headers[i];
\r
677 - value = notmuch_message_get_header (message, name);
\r
679 - printf ("%s: %s\n", name, value);
\r
680 + disposition = g_mime_object_get_content_disposition (part);
\r
681 + if (disposition &&
\r
682 + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
\r
684 + const char *filename = g_mime_part_get_filename (GMIME_PART (part));
\r
686 + printf (", \"filename\": %s", json_quote_str (ctx, filename));
\r
689 - printf ("\fheader}\n");
\r
690 - printf ("\fbody{\n");
\r
691 + if (g_mime_content_type_is_type (content_type, "text", "*") &&
\r
692 + !g_mime_content_type_is_type (content_type, "text", "html"))
\r
694 + show_part_content (part, stream_memory);
\r
695 + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
\r
697 + printf (", \"content\": %s", json_quote_str (ctx, (char *) part_content->data));
\r
700 + fputs ("}", stdout);
\r
702 + talloc_free (ctx);
\r
703 + if (stream_memory)
\r
704 + g_object_unref (stream_memory);
\r
708 +show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)
\r
710 + fputs (format->message_start, stdout);
\r
711 + format->message(ctx, message, indent);
\r
713 - show_message_body (notmuch_message_get_filename (message), show_part);
\r
714 + fputs (format->header_start, stdout);
\r
715 + format->header(ctx, message);
\r
716 + fputs (format->header_end, stdout);
\r
718 - printf ("\fbody}\n");
\r
719 + fputs (format->body_start, stdout);
\r
720 + show_message_body (notmuch_message_get_filename (message), format->part);
\r
721 + fputs (format->body_end, stdout);
\r
723 - printf ("\fmessage}\n");
\r
724 + fputs (format->message_end, stdout);
\r
729 -show_messages (void *ctx, notmuch_messages_t *messages, int indent,
\r
730 +show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,
\r
731 notmuch_bool_t entire_thread)
\r
733 notmuch_message_t *message;
\r
734 notmuch_bool_t match;
\r
735 + int first_set = 1;
\r
738 + fputs (format->message_set_start, stdout);
\r
741 notmuch_messages_has_more (messages);
\r
742 notmuch_messages_advance (messages))
\r
745 + fputs (format->message_set_sep, stdout);
\r
748 + fputs (format->message_set_start, stdout);
\r
750 message = notmuch_messages_get (messages);
\r
752 match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
\r
753 @@ -202,15 +368,21 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent,
\r
754 next_indent = indent;
\r
756 if (match || entire_thread) {
\r
757 - show_message (ctx, message, indent);
\r
758 + show_message (ctx, format, message, indent);
\r
759 next_indent = indent + 1;
\r
761 + fputs (format->message_set_sep, stdout);
\r
764 - show_messages (ctx, notmuch_message_get_replies (message),
\r
765 + show_messages (ctx, format, notmuch_message_get_replies (message),
\r
766 next_indent, entire_thread);
\r
768 notmuch_message_destroy (message);
\r
770 + fputs (format->message_set_end, stdout);
\r
773 + fputs (format->message_set_end, stdout);
\r
777 @@ -223,15 +395,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
778 notmuch_thread_t *thread;
\r
779 notmuch_messages_t *messages;
\r
780 char *query_string;
\r
782 + const show_format_t *format = &format_text;
\r
783 int entire_thread = 0;
\r
785 + int first_toplevel = 1;
\r
787 for (i = 0; i < argc && argv[i][0] == '-'; i++) {
\r
788 if (strcmp (argv[i], "--") == 0) {
\r
792 - if (strcmp(argv[i], "--entire-thread") == 0) {
\r
793 + if (STRNCMP_LITERAL (argv[i], "--output=") == 0) {
\r
794 + opt = argv[i] + sizeof ("--output=") - 1;
\r
795 + if (strcmp (opt, "text") == 0) {
\r
796 + format = &format_text;
\r
797 + } else if (strcmp (opt, "json") == 0) {
\r
798 + format = &format_json;
\r
799 + entire_thread = 1;
\r
801 + fprintf (stderr, "Invalid value for --output: %s\n", opt);
\r
804 + } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
\r
807 fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
\r
808 @@ -268,6 +454,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
812 + fputs (format->message_set_start, stdout);
\r
814 for (threads = notmuch_query_search_threads (query);
\r
815 notmuch_threads_has_more (threads);
\r
816 notmuch_threads_advance (threads))
\r
817 @@ -280,11 +468,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
\r
818 INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
\r
819 notmuch_thread_get_thread_id (thread));
\r
821 - show_messages (ctx, messages, 0, entire_thread);
\r
822 + if (!first_toplevel)
\r
823 + fputs (format->message_set_sep, stdout);
\r
824 + first_toplevel = 0;
\r
826 + show_messages (ctx, format, messages, 0, entire_thread);
\r
828 notmuch_thread_destroy (thread);
\r
832 + fputs (format->message_set_end, stdout);
\r
834 notmuch_query_destroy (query);
\r
835 notmuch_database_close (notmuch);
\r
837 diff --git a/notmuch.c b/notmuch.c
\r
838 index 2ac8a59..aa2fc12 100644
\r
841 @@ -162,6 +162,11 @@ command_t commands[] = {
\r
843 "\t\tSupported options for search include:\n"
\r
845 + "\t\t--output=(json|text)\n"
\r
847 + "\t\t\tPresents the results in either JSON or plain-text\n"
\r
848 + "\t\t\tformat, which is the default.\n"
\r
850 "\t\t--sort=(newest-first|oldest-first)\n"
\r
852 "\t\t\tPresent results in either chronological order\n"
\r
853 @@ -186,13 +191,18 @@ command_t commands[] = {
\r
854 "\t\t\tall messages in the same thread as any matched\n"
\r
855 "\t\t\tmessage will be displayed.\n"
\r
857 - "\t\tThe output format is plain-text, with all text-content\n"
\r
858 - "\t\tMIME parts decoded. Various components in the output,\n"
\r
859 - "\t\t('message', 'header', 'body', 'attachment', and MIME 'part')\n"
\r
860 - "\t\tare delimited by easily-parsed markers. Each marker consists\n"
\r
861 - "\t\tof a Control-L character (ASCII decimal 12), the name of\n"
\r
862 - "\t\tthe marker, and then either an opening or closing brace,\n"
\r
863 - "\t\t'{' or '}' to either open or close the component.\n"
\r
864 + "\t\t--output=(json|text)\n"
\r
866 + "\t\t\tPresents the results in either JSON or plain-text\n"
\r
867 + "\t\t\tformat, which is the default.\n"
\r
869 + "\t\tThe plain-text has all text-content MIME parts decoded.\n"
\r
870 + "\t\tVarious components in the output, ('message', 'header',\n"
\r
871 + "\t\t'body', 'attachment', and MIME 'part') are delimited by\n"
\r
872 + "\t\teasily-parsed markers. Each marker consists of a Control-L\n"
\r
873 + "\t\tcharacter (ASCII decimal 12), the name of the marker, and\n"
\r
874 + "\t\tthen either an opening or closing brace, '{' or '}' to\n"
\r
875 + "\t\teither open or close the component.\n"
\r
877 "\t\tA common use of \"notmuch show\" is to display a single\n"
\r
878 "\t\tthread of email messages. For this, use a search term of\n"
\r
879 diff --git a/show-message.c b/show-message.c
\r
880 index 784981b..05ced9c 100644
\r
881 --- a/show-message.c
\r
882 +++ b/show-message.c
\r
883 @@ -26,8 +26,6 @@ static void
\r
884 show_message_part (GMimeObject *part, int *part_count,
\r
885 void (*show_part) (GMimeObject *part, int *part_count))
\r
887 - *part_count = *part_count + 1;
\r
889 if (GMIME_IS_MULTIPART (part)) {
\r
890 GMimeMultipart *multipart = GMIME_MULTIPART (part);
\r
892 @@ -56,6 +54,8 @@ show_message_part (GMimeObject *part, int *part_count,
\r
896 + *part_count = *part_count + 1;
\r
898 (*show_part) (part, part_count);
\r
901 Scott Robinson | http://quadhome.com/
\r
903 Q: Why are my replies five sentences or less?
\r
904 A: http://five.sentenc.es/
\r