[DRE-maint] Bug#561839: edit-json: prettify_json fails on input that edit_json can open

David Bremner bremner at unb.ca
Sun Dec 20 16:13:37 UTC 2009


Package: edit-json
Version: 1.1.9-1
Severity: normal

The attached JSON file causes an expception to be thrown in prettify_json, 
but can be opened ok with edit_json.  Of course it is conceivable the json is 
syntactically incorrect (that is what I wanted prettify_json for :) ).

/usr/lib/ruby/1.8/json/common.rb:122:in `parse': nesting of 20 is too deep (JSON::NestingError)
	from /usr/lib/ruby/1.8/json/common.rb:122:in `parse'
	from /usr/lib/ruby/1.8/json/common.rb:13:in `[]'
	from /usr/bin/prettify_json:56

-- System Information:
Debian Release: squeeze/sid
  APT prefers unstable
  APT policy: (900, 'unstable')
Architecture: i386 (i686)

Kernel: Linux 2.6.31-1-686 (SMP w/2 CPU cores)
Locale: LANG=en_CA.UTF-8, LC_CTYPE=en_CA.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages edit-json depends on:
ii  libgtk2-ruby1.8              0.19.3-1    GTK+ bindings for the Ruby languag
ii  libjson-ruby1.8              1.1.9-1     JSON library for Ruby (Ruby 1.8 ve
ii  ruby1.8                      1.8.7.174-3 Interpreter of object-oriented scr

edit-json recommends no packages.

edit-json suggests no packages.

-- no debconf information
-------------- next part --------------
[[[{"id": "87iqcapijl.fsf at pivot.cs.unb.ca", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260746482.23128.pivot.cs.unb.ca:2,", "headers": {"Subject": "[notmuch] RFC: output json from notmuch?", "From": "David Bremner <david at tethera.net>", "To": "notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Sun, 13 Dec 2009 19:21:02 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "\nIt would be nice to have more structured output from notmuch-show.  I\ndecided to investigate sexp (i.e. lisp) and json output.\n\nSexp has the obvious advantage that it is trivially parsable in emacs;\nfor other clients it is a little more work.  I started looking at\nsfsexp (http://sexpr.sourceforge.net).  It looks ok; I got a little\nirritated that it doesn't support cons cells (i.e. dotted pairs). \n\nThen I found that json parsing is provided by the library json.el\nshipped with emacs23, so I decided to play with json a bit.\n\nI settled on the jansson library (http://www.digip.org/jansson/)\nbecause it seemed to have a sane api and documentation, but there are\nmany choices.  I'll follow up with the actual patch, but the idea is to\nreplace the printfs in show_message with calls to set (key,value) pairs\nin a json object, and output it at the end.\n\nThis is not in any sense a production patch (e.g. it needs to actually\nreturn a thread object rather than just dumping messages out; nothing at\nall has been done on the emacs side), but it gives you some idea of\nwould be involved.\n\nSo, do people think this is a reasonable idea to persue?\n\n    1) it adds a dependency, but not a heavy one. jansson is about 300k\n    installed on debian/i386\n\n    2) It might mean that people using emacs22 might have to score\n    json.el from somewhere; I'm not sure.\n\n    3) There is some increase in memory use since the whole thread has\n    to be built as a json object before being output.\n\n    4) Of course jansson is doing it's own reference counting memory\n    managment, and not using talloc. But we already have glib...\n\nOf course we won't really know if it is good idea until we try it, but\nif it already looks like a no-go, I'll stop.\n\nAttachments:  json output of a message from carl, and the equivalent\nsexpr as parsed by json.el.  \n\n\n\n\n-- \nDavid Bremner                                  Professor, UNB Computer Science\nbremner at unb.ca\t\t\t           \nhttp://www.cs.unb.ca/~bremner               Cross Appointment, UNB Mathematics\nhttp://www.mitacs.ca/\t\t\t   MITACS Atlantic Scientific Director\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, [[{"id": "87hbrupib9.fsf at pivot.cs.unb.ca", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260746780.23154.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "David Bremner <david at tethera.net>", "To": "notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Sun, 13 Dec 2009 19:26:02 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Sun, 13 Dec 2009 19:21:02 -0400, David Bremner <david at tethera.net> wrote:\n> \n> Attachments:  json output of a message from carl, and the equivalent\n> sexpr as parsed by json.el.  \n> \nuh, right. Second time lucky.\n"}, {"id": 2, "content-type": "application/octet-stream", "filename": "msg.json"}, {"id": 3, "content-type": "application/octet-stream", "filename": "msg.sexpr"}, {"id": 4, "content-type": "text/plain", "content": "\n-- \nDavid Bremner                                  Professor, UNB Computer Science\nbremner at unb.ca\t\t\t           \nhttp://www.cs.unb.ca/~bremner               Cross Appointment, UNB Mathematics\nhttp://www.mitacs.ca/\t\t\t   MITACS Atlantic Scientific Director\n"}, {"id": 5, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n"}]}, [[{"id": "1260749055-sup-6010 at lisa", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260749126.23341.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "Scott Robinson <scott at quadhome.com>", "To": "notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Sun, 13 Dec 2009 16:05:07 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "Excerpts from David Bremner's message of Sun Dec 13 15:26:02 -0800 2009:\n> On Sun, 13 Dec 2009 19:21:02 -0400, David Bremner <david at tethera.net> wrote:\n> > \n> > Attachments:  json output of a message from carl, and the equivalent\n> > sexpr as parsed by json.el.  \n> > \n> uh, right. Second time lucky.\n> \n\nI have a patch for a --output=(text|json) for both notmuch-show and\nnotmuch-search. I mentioned it earlier on the list, and no one seemed to have\nany interest.\n\nIs it worth updating to HEAD and trying again?\n-- \nScott Robinson | http://quadhome.com/\n\nQ: Why are my replies five sentences or less?\nA: http://five.sentenc.es/\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, [[{"id": "87eimypesg.fsf at pivot.cs.unb.ca", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260751353.23463.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "David Bremner <bremner at unb.ca>", "To": "Scott Robinson <scott at quadhome.com>, notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Sun, 13 Dec 2009 20:42:07 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Sun, 13 Dec 2009 16:05:07 -0800, Scott Robinson <scott at quadhome.com> wrote:\n\n> I have a patch for a --output=(text|json) for both notmuch-show and\n> notmuch-search. I mentioned it earlier on the list, and no one seemed to have\n> any interest.\n\nAhh, I missed that. I think it was just before I subscribed to the list,\nand I skipped over the subject about perl and python.\n\n> Is it worth updating to HEAD and trying again?\n\nSure, it sounds more complete than what I have. You could also wait and\nsee what feedback this thread gathers.\n\nOn a different topic (and in response to Carl and your earlier\ndiscussion), as a counter-weight to the desire to avoid dependencies\n(which I agree with), I also think we should be careful about how many\nembedded copies of code there are, from the point of keeping up with\nsecurity problems.  This is probably a bias I picked up from Debian.\n\n\n-- \nDavid Bremner                                  Professor, UNB Computer Science\nbremner at unb.ca\t\t\t           \nhttp://www.cs.unb.ca/~bremner               Cross Appointment, UNB Mathematics\nhttp://www.mitacs.ca/\t\t\t   MITACS Atlantic Scientific Director\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, [[{"id": "87hbrt2n64.fsf at yoom.home.cworth.org", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260830528.11619.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "Carl Worth <cworth at cworth.org>", "To": "David Bremner <bremner at unb.ca>, Scott Robinson <scott at quadhome.com>, notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Mon, 14 Dec 2009 14:41:55 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Sun, 13 Dec 2009 20:42:07 -0400, David Bremner <bremner at unb.ca> wrote:\n> On a different topic (and in response to Carl and your earlier\n> discussion), as a counter-weight to the desire to avoid dependencies\n> (which I agree with), I also think we should be careful about how many\n> embedded copies of code there are, from the point of keeping up with\n> security problems.  This is probably a bias I picked up from Debian.\n\nEmbedded copies of code already packaged in Debian is something to\navoid, definitely. I'm not actively against having dependencies for\nfunctionality that makes sense.\n\nFor example, the current libsha1 code in notmuch has been the subject of\na debate about embedded code copies here on the list already. If\nsomebody would like to maintain that code as a Debian package, then I\nwould be happy to depend on it that way rather than having an embedded\ncopy inside notmuch.\n\nFor something like JSON output, I really can't see how we need an\nexternal library. The job we're talking about is changing our current\ndelimiters and then fixing our code to properly quote delimiters\nappearing in the content. That doesn't sound like a job that needs a\nlibrary.\n\n(Meanwhile, if we were parsing JSON, then I'd be happy to depend on a\nlibrary for that.)\n\n-Carl\n"}, {"id": 2, "content-type": "application/pgp-signature"}, {"id": 3, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n"}]}, []]]], [{"id": "87iqc92ni8.fsf at yoom.home.cworth.org", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260830093.11577.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "Carl Worth <cworth at cworth.org>", "To": "Scott Robinson <scott at quadhome.com>, notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Mon, 14 Dec 2009 14:34:39 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Sun, 13 Dec 2009 16:05:07 -0800, Scott Robinson <scott at quadhome.com> wrote:\n> I have a patch for a --output=(text|json) for both notmuch-show and\n> notmuch-search. I mentioned it earlier on the list, and no one seemed to have\n> any interest.\n\nHi Scott,\n\nI remember you mentioning this earlier, but I didn't remember seeing the\nactual patch? Did I miss it? I also didn't find it again when searching\nand re-reading the thread. hopefully this isn't a case of my mail\nclient's search feature failing me... ;-)\n\n> Is it worth updating to HEAD and trying again?\n\nIf you'd like to, please feel free. I'd be glad to evaluate different\noptions here.\n\n-Carl\n.org0"}, {"id": 2, "content-type": "application/pgp-signature"}, {"id": 3, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\ning again?\n\nIf you'd like to, please feel free. I'd be glad to evaluate different\noptions here.\n\n-Carl\n.org0"}]}, []]]]]], [{"id": "1260748780-sup-2537 at gopher.local", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260749282.23348.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "Marten Veldthuis <marten at veldthuis.com>", "To": "notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Mon, 14 Dec 2009 01:07:45 +0100"}, "body": [{"id": 1, "content-type": "text/plain", "content": "Excerpts from David Bremner's message of Mon Dec 14 00:21:02 +0100 2009:\n> So, do people think this is a reasonable idea to persue?\n\nJSON was actually the first thing I thought of when I first saw the\noutput of notmuch-show. I think it's a much more natural fit for notmuch\nthan say, sexps, since most languages will have libraries for JSON,\nwhich will be nicer to interfaces other than the emacs one.\n-- \n- Marten\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\nard.nearlyfreespeech.net>)\tid 1NKJVE-00030g-Np\tfor bremner at pivot.cs.unb.ca; Mon, 14 Dec 2009 18:34:53 -0400"}]}, []], [{"id": "87k4wp2noq.fsf at yoom.home.cworth.org", "match": false, "filename": "/home/bremner/Maildir/.quarantine/new/1260829857.11562.pivot.cs.unb.ca", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "Carl Worth <cworth at cworth.org>", "To": "David Bremner <david at tethera.net>, notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Mon, 14 Dec 2009 14:30:45 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Sun, 13 Dec 2009 19:21:02 -0400, David Bremner <david at tethera.net> wrote:\n> It would be nice to have more structured output from notmuch-show.  I\n> decided to investigate sexp (i.e. lisp) and json output.\n\nThanks, David!\n\n> Then I found that json parsing is provided by the library json.el\n> shipped with emacs23, so I decided to play with json a bit.\n\nThat sounds very promising. I wasn't aware this existed inside emacs\nalready.\n\n> I settled on the jansson library (http://www.digip.org/jansson/)\n> because it seemed to have a sane api and documentation, but there are\n> many choices.  I'll follow up with the actual patch, but the idea is to\n> replace the printfs in show_message with calls to set (key,value) pairs\n> in a json object, and output it at the end.\n\nI'm still not sure of the need to depend on a library just to *generate*\nany particular format. I would expect that job to be so constrained as\nto be almost trivial. I won't necessarily block a patch based on that,\nbut I think we should at least look at how easy it would be to just emit\nthe syntax manually.\n\n> So, do people think this is a reasonable idea to persue?\n\nI do, yes. The current lack of full quoting is an obnoxious bug.\n\n>     1) it adds a dependency, but not a heavy one. jansson is about 300k\n>     installed on debian/i386\n\nThat might be acceptable. And we might rewrite the patch to avoid it as\nwell. I'd like to see what that would take.\n\n>     2) It might mean that people using emacs22 might have to score\n>     json.el from somewhere; I'm not sure.\n\nCurrently, it sounds like notmuch is entirely unusable from emacs22, so\nI'm really not too concerned about one more thing that requires emacs\n23.\n\n>     3) There is some increase in memory use since the whole thread has\n>     to be built as a json object before being output.\n\nRight. That would be another reason to just stream the output\nmanually. The syntax doesn't require things like knowing the size of an\narray before emitting the first element, right?\n\n>     4) Of course jansson is doing it's own reference counting memory\n>     managment, and not using talloc. But we already have glib...\n\nIt's not evil to not use talloc. So not an issue there.\n\n-Carl\n"}, {"id": 2, "content-type": "application/pgp-signature"}]}, [[{"id": "87skbdcfu3.fsf at pivot.cs.unb.ca", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260832232.11831.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] RFC: output json from notmuch?", "From": "David Bremner <david at tethera.net>", "To": "Carl Worth <cworth at cworth.org>, notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Mon, 14 Dec 2009 19:10:12 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Mon, 14 Dec 2009 14:30:45 -0800, Carl Worth <cworth at cworth.org> wrote:\n\n> I'm still not sure of the need to depend on a library just to *generate*\n> any particular format. I would expect that job to be so constrained as\n> to be almost trivial. I won't necessarily block a patch based on that,\n> but I think we should at least look at how easy it would be to just emit\n> the syntax manually.\n\nOK, I'll wait for Scott's patch, and see how he is using cJSON. Just\nstealing a few functions might be the way to go.\n\nThe cJSON code is less horrifying after running through indent. Now all\nwe need is \"indent --carl\" :)\n\nd\n\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n Dec 2009 14:34:42 -0800 (PST)p"}]}, [[{"id": "1261114167-sup-8228 at lisa", "match": true, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261114457.9181.pivot.cs.unb.ca", "headers": {"Subject": "[notmuch] [PATCH] JSON output for notmuch-search and notmuch-show.", "From": "Scott Robinson <scott at quadhome.com>", "To": "notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Thu, 17 Dec 2009 21:33:54 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "Excerpts from David Bremner's message of Mon Dec 14 15:10:12 -0800 2009:\n> OK, I'll wait for Scott's patch, and see how he is using cJSON. Just\n> stealing a few functions might be the way to go.\n> \n> The cJSON code is less horrifying after running through indent. Now all\n> we need is \"indent --carl\" :)\n> \n\nI took an earlier suggestion and didn't use cJSON, instead writing custom code\nfor emitting the new format.\n\n\n\nAdded an \"--output=(json|text|)\" command-line option to both\nnotmuch-search and notmuch-show.\n\nIn the case of notmuch-show, \"--output=json\" also implies\n\"--entire-thread\" as the thread structure is implicit in the emitted\ndocument tree.\n\nAs a coincidence to the implementation, multipart message ID numbers are\nnow incremented with each part printed. This changes the previous\nsemantics, which were unclear and not necessary related to the actual\nordering of the message parts.\n---\n Makefile.local   |    3 +-\n json.c           |   73 ++++++++++++++\n notmuch-client.h |    3 +\n notmuch-search.c |  163 +++++++++++++++++++++++++++++---\n notmuch-show.c   |  275 ++++++++++++++++++++++++++++++++++++++++++++++--------\n notmuch.c        |   24 ++++--\n show-message.c   |    4 +-\n 7 files changed, 481 insertions(+), 64 deletions(-)\n create mode 100644 json.c\n\ndiff --git a/Makefile.local b/Makefile.local\nindex 933ff4c..53b474b 100644\n--- a/Makefile.local\n+++ b/Makefile.local\n@@ -18,7 +18,8 @@ notmuch_client_srcs =\t\t\\\n \tnotmuch-tag.c\t\t\\\n \tnotmuch-time.c\t\t\\\n \tquery-string.c\t\t\\\n-\tshow-message.c\n+\tshow-message.c\t\t\\\n+\tjson.c\n \n notmuch_client_modules = $(notmuch_client_srcs:.c=.o)\n notmuch: $(notmuch_client_modules) lib/notmuch.a\ndiff --git a/json.c b/json.c\nnew file mode 100644\nindex 0000000..ee563d6\n--- /dev/null\n+++ b/json.c\n@@ -0,0 +1,73 @@\n+/* notmuch - Not much of an email program, (just index and search)\n+ *\n+ * Copyright  2009 Carl Worth\n+ * Copyright  2009 Keith Packard\n+ *\n+ * This program is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation, either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * along with this program.  If not, see http://www.gnu.org/licenses/ .\n+ *\n+ * Authors: Carl Worth <cworth at cworth.org>\n+ *\t    Keith Packard <keithp at keithp.com>\n+ */\n+\n+#include \"notmuch-client.h\"\n+\n+/*\n+ * json_quote_str derived from cJSON's print_string_ptr,\n+ * Copyright (c) 2009 Dave Gamble\n+ */\n+\n+char *\n+json_quote_str(const void *ctx, const char *str)\n+{\n+    const char *ptr;\n+    char *ptr2;\n+    char *out;\n+    int len = 0;\n+\n+    if (!str)\n+\treturn NULL;\n+\n+    for (ptr = str; *ptr; len++, ptr++) {\n+\tif (*ptr < 32 || *ptr == '\\\"' || *ptr == '\\\\')\n+\t    len++;\n+    }\n+\n+    out = talloc_array (ctx, char, len + 3);\n+\n+    ptr = str;\n+    ptr2 = out;\n+\n+    *ptr2++ = '\\\"';\n+    while (*ptr) {\n+\t    if (*ptr > 31 && *ptr != '\\\"' && *ptr != '\\\\') {\n+\t\t*ptr2++ = *ptr++;\n+\t    } else {\n+\t\t*ptr2++ = '\\\\';\n+\t\tswitch (*ptr++) {\n+\t\t    case '\\\"':\t*ptr2++ = '\\\"';\tbreak;\n+\t\t    case '\\\\':\t*ptr2++ = '\\\\';\tbreak;\n+\t\t    case '\\b':\t*ptr2++ = 'b';\tbreak;\n+\t\t    case '\\f':\t*ptr2++ = 'f';\tbreak;\n+\t\t    case '\\n':\t*ptr2++ = 'n';\tbreak;\n+\t\t    case '\\r':\t*ptr2++ = 'r';\tbreak;\n+\t\t    case '\\t':\t*ptr2++ = 't';\tbreak;\n+\t\t    default:\t ptr2--;\tbreak;\n+\t\t}\n+\t    }\n+    }\n+    *ptr2++ = '\\\"';\n+    *ptr2++ = '\\0';\n+\n+    return out;\n+}\ndiff --git a/notmuch-client.h b/notmuch-client.h\nindex 50a30fe..7b844b9 100644\n--- a/notmuch-client.h\n+++ b/notmuch-client.h\n@@ -143,6 +143,9 @@ notmuch_status_t\n show_message_body (const char *filename,\n \t\t   void (*show_part) (GMimeObject *part, int *part_count));\n \n+char *\n+json_quote_str (const void *ctx, const char *str);\n+\n /* notmuch-config.c */\n \n typedef struct _notmuch_config notmuch_config_t;\ndiff --git a/notmuch-search.c b/notmuch-search.c\nindex dc44eb6..e243747 100644\n--- a/notmuch-search.c\n+++ b/notmuch-search.c\n@@ -20,8 +20,120 @@\n \n #include \"notmuch-client.h\"\n \n+typedef struct search_format {\n+    const char *results_start;\n+    const char *thread_start;\n+    void (*thread) (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject);\n+    const char *tag_start;\n+    const char *tag;\n+    const char *tag_sep;\n+    const char *tag_end;\n+    const char *thread_sep;\n+    const char *thread_end;\n+    const char *results_end;\n+} search_format_t;\n+\n+static void\n+format_thread_text (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject);\n+static const search_format_t format_text = {\n+    \"\",\n+\t\"\",\n+\t    format_thread_text,\n+\t    \" (\",\n+\t\t\"%s\", \" \",\n+\t    \")\", \"\",\n+\t\"\\n\",\n+    \"\",\n+};\n+\n+static void\n+format_thread_json (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject);\n+static const search_format_t format_json = {\n+    \"[\",\n+\t\"{\",\n+\t    format_thread_json,\n+\t    \"\\\"tags\\\": [\",\n+\t\t\"\\\"%s\\\"\", \", \",\n+\t    \"]\", \",\\n\",\n+\t\"}\",\n+    \"]\\n\",\n+};\n+\n+static void\n+format_thread_text (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject)\n+{\n+    printf (\"thread:%s %12s [%d/%d] %s; %s\",\n+\t    id,\n+\t    notmuch_time_relative_date (ctx, date),\n+\t    matched,\n+\t    total,\n+\t    authors,\n+\t    subject);\n+}\n+\n+static void\n+format_thread_json (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject)\n+{\n+    struct tm *tm;\n+    char timestamp[40];\n+    void *ctx_quote = talloc_new (ctx);\n+\n+    tm = gmtime (&date);\n+    if (tm == NULL)\n+\tINTERNAL_ERROR (\"gmtime failed on thread %s.\", id);\n+\n+    if (strftime (timestamp, sizeof (timestamp), \"%s\", tm) == 0)\n+\tINTERNAL_ERROR (\"strftime failed on thread %s.\", id);\n+\n+    printf (\"\\\"id\\\": %s,\\n\"\n+\t    \"\\\"timestamp\\\": %s,\\n\"\n+\t    \"\\\"matched\\\": %d,\\n\"\n+\t    \"\\\"total\\\": %d,\\n\"\n+\t    \"\\\"authors\\\": %s,\\n\"\n+\t    \"\\\"subject\\\": %s,\\n\",\n+\t    json_quote_str (ctx_quote, id),\n+\t    timestamp,\n+\t    matched,\n+\t    total,\n+\t    json_quote_str (ctx_quote, authors),\n+\t    json_quote_str (ctx_quote, subject));\n+\n+    talloc_free (ctx_quote);\n+}\n+\n static void\n do_search_threads (const void *ctx,\n+\t\t   const search_format_t *format,\n \t\t   notmuch_query_t *query,\n \t\t   notmuch_sort_t sort)\n {\n@@ -29,7 +141,9 @@ do_search_threads (const void *ctx,\n     notmuch_threads_t *threads;\n     notmuch_tags_t *tags;\n     time_t date;\n-    const char *relative_date;\n+    int first_thread = 1;\n+\n+    fputs (format->results_start, stdout);\n \n     for (threads = notmuch_query_search_threads (query);\n \t notmuch_threads_has_more (threads);\n@@ -37,6 +151,9 @@ do_search_threads (const void *ctx,\n     {\n \tint first_tag = 1;\n \n+\tif (! first_thread)\n+\t    fputs (format->thread_sep, stdout);\n+\n \tthread = notmuch_threads_get (threads);\n \n \tif (sort == NOTMUCH_SORT_OLDEST_FIRST)\n@@ -44,30 +161,37 @@ do_search_threads (const void *ctx,\n \telse\n \t    date = notmuch_thread_get_newest_date (thread);\n \n-\trelative_date = notmuch_time_relative_date (ctx, date);\n+\tfputs (format->thread_start, stdout);\n+\n+\tformat->thread (ctx,\n+\t\t\tnotmuch_thread_get_thread_id (thread),\n+\t\t\tdate,\n+\t\t\tnotmuch_thread_get_matched_messages (thread),\n+\t\t\tnotmuch_thread_get_total_messages (thread),\n+\t\t\tnotmuch_thread_get_authors (thread),\n+\t\t\tnotmuch_thread_get_subject (thread));\n \n-\tprintf (\"thread:%s %12s [%d/%d] %s; %s\",\n-\t\tnotmuch_thread_get_thread_id (thread),\n-\t\trelative_date,\n-\t\tnotmuch_thread_get_matched_messages (thread),\n-\t\tnotmuch_thread_get_total_messages (thread),\n-\t\tnotmuch_thread_get_authors (thread),\n-\t\tnotmuch_thread_get_subject (thread));\n+\tfputs (format->tag_start, stdout);\n \n-\tprintf (\" (\");\n \tfor (tags = notmuch_thread_get_tags (thread);\n \t     notmuch_tags_has_more (tags);\n \t     notmuch_tags_advance (tags))\n \t{\n \t    if (! first_tag)\n-\t\tprintf (\" \");\n-\t    printf (\"%s\", notmuch_tags_get (tags));\n+\t\tfputs (format->tag_sep, stdout);\n+\t    printf (format->tag, notmuch_tags_get (tags));\n \t    first_tag = 0;\n \t}\n-\tprintf (\")\\n\");\n+\n+\tfputs (format->tag_end, stdout);\n+\tfputs (format->thread_end, stdout);\n+\n+\tfirst_thread = 0;\n \n \tnotmuch_thread_destroy (thread);\n     }\n+\n+    fputs (format->results_end, stdout);\n }\n \n int\n@@ -79,6 +203,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n     char *query_str;\n     char *opt;\n     notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;\n+    const search_format_t *format = &format_text;\n     int i;\n \n     for (i = 0; i < argc && argv[i][0] == '-'; i++) {\n@@ -96,6 +221,16 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n \t\tfprintf (stderr, \"Invalid value for --sort: %s\\n\", opt);\n \t\treturn 1;\n \t    }\n+\t} else if (STRNCMP_LITERAL (argv[i], \"--output=\") == 0) {\n+\t    opt = argv[i] + sizeof (\"--output=\") - 1;\n+\t    if (strcmp (opt, \"text\") == 0) {\n+\t\tformat = &format_text;\n+\t    } else if (strcmp (opt, \"json\") == 0) {\n+\t\tformat = &format_json;\n+\t    } else {\n+\t\tfprintf (stderr, \"Invalid value for --output: %s\\n\", opt);\n+\t\treturn 1;\n+\t    }\n \t} else {\n \t    fprintf (stderr, \"Unrecognized option: %s\\n\", argv[i]);\n \t    return 1;\n@@ -132,7 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n \n     notmuch_query_set_sort (query, sort);\n \n-    do_search_threads (ctx, query, sort);\n+    do_search_threads (ctx, format, query, sort);\n \n     notmuch_query_destroy (query);\n     notmuch_database_close (notmuch);\ndiff --git a/notmuch-show.c b/notmuch-show.c\nindex 376aacd..70d2605 100644\n--- a/notmuch-show.c\n+++ b/notmuch-show.c\n@@ -20,8 +20,65 @@\n \n #include \"notmuch-client.h\"\n \n+typedef struct show_format {\n+    const char *message_set_start;\n+    const char *message_start;\n+    void (*message) (const void *ctx,\n+\t\t     notmuch_message_t *message,\n+\t\t     int indent);\n+    const char *header_start;\n+    void (*header) (const void *ctx,\n+\t\t    notmuch_message_t *message);\n+    const char *header_end;\n+    const char *body_start;\n+    void (*part) (GMimeObject *part,\n+\t\t  int *part_count);\n+    const char *body_end;\n+    const char *message_end;\n+    const char *message_set_sep;\n+    const char *message_set_end;\n+} show_format_t;\n+\n+static void\n+format_message_text (unused (const void *ctx),\n+\t\t     notmuch_message_t *message,\n+\t\t     int indent);\n+static void\n+format_headers_text (const void *ctx,\n+\t\t     notmuch_message_t *message);\n+static void\n+format_part_text (GMimeObject *part,\n+\t\t  int *part_count);\n+static const show_format_t format_text = {\n+    \"\",\n+\t\"\\fmessage{ \", format_message_text,\n+\t    \"\\fheader{\\n\", format_headers_text, \"\\fheader}\\n\",\n+\t    \"\\fbody{\\n\", format_part_text, \"\\fbody}\\n\",\n+\t\"\\fmessage}\\n\", \"\",\n+    \"\"\n+};\n+\n+static void\n+format_message_json (const void *ctx,\n+\t\t     notmuch_message_t *message,\n+\t\t     unused (int indent));\n+static void\n+format_headers_json (const void *ctx,\n+\t\t     notmuch_message_t *message);\n+static void\n+format_part_json (GMimeObject *part,\n+\t\t  int *part_count);\n+static const show_format_t format_json = {\n+    \"[\",\n+\t\"{\", format_message_json,\n+\t    \", \\\"headers\\\": {\", format_headers_json, \"}\",\n+\t    \", \\\"body\\\": [\", format_part_json, \"]\",\n+\t\"}\", \", \",\n+    \"]\"\n+};\n+\n static const char *\n-_get_tags_as_string (void *ctx, notmuch_message_t *message)\n+_get_tags_as_string (const void *ctx, notmuch_message_t *message)\n {\n     notmuch_tags_t *tags;\n     int first = 1;\n@@ -48,7 +105,7 @@ _get_tags_as_string (void *ctx, notmuch_message_t *message)\n \n /* Get a nice, single-line summary of message. */\n static const char *\n-_get_one_line_summary (void *ctx, notmuch_message_t *message)\n+_get_one_line_summary (const void *ctx, notmuch_message_t *message)\n {\n     const char *from;\n     time_t date;\n@@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)\n }\n \n static void\n-show_part_content (GMimeObject *part)\n+format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)\n+{\n+    printf (\"id:%s depth:%d match:%d filename:%s\\n\",\n+\t    notmuch_message_get_message_id (message),\n+\t    indent,\n+\t    notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),\n+\t    notmuch_message_get_filename (message));\n+}\n+\n+static void\n+format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))\n+{\n+    void *ctx_quote = talloc_new (ctx);\n+    \n+    printf (\"\\\"id\\\": %s, \\\"match\\\": %s, \\\"filename\\\": %s\",\n+\t    json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),\n+\t    notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? \"true\" : \"false\",\n+\t    json_quote_str (ctx_quote, notmuch_message_get_filename (message)));\n+\n+    talloc_free (ctx_quote);\n+}\n+\n+static void\n+format_headers_text (const void *ctx, notmuch_message_t *message)\n+{\n+    const char *headers[] = {\n+\t\"Subject\", \"From\", \"To\", \"Cc\", \"Bcc\", \"Date\"\n+    };\n+    const char *name, *value;\n+    unsigned int i;\n+\n+    printf (\"%s\\n\", _get_one_line_summary (ctx, message));\n+\n+    for (i = 0; i < ARRAY_SIZE (headers); i++) {\n+\tname = headers[i];\n+\tvalue = notmuch_message_get_header (message, name);\n+\tif (value)\n+\t    printf (\"%s: %s\\n\", name, value);\n+    }\n+}\n+\n+static void\n+format_headers_json (const void *ctx, notmuch_message_t *message)\n+{\n+    const char *headers[] = {\n+\t\"Subject\", \"From\", \"To\", \"Cc\", \"Bcc\", \"Date\"\n+    };\n+    const char *name, *value;\n+    unsigned int i;\n+    int first_header = 1;\n+    void *ctx_quote = talloc_new (ctx);\n+\n+    for (i = 0; i < ARRAY_SIZE (headers); i++) {\n+\tname = headers[i];\n+\tvalue = notmuch_message_get_header (message, name);\n+\tif (value)\n+\t{\n+\t    if (!first_header)\n+\t\tfputs (\", \", stdout);\n+\t    first_header = 0;\n+\n+\t    printf (\"%s: %s\",\n+\t\t    json_quote_str (ctx_quote, name),\n+\t\t    json_quote_str (ctx_quote, value));\n+\t}\n+    }\n+\n+    talloc_free (ctx_quote);\n+}\n+\n+static void\n+show_part_content (GMimeObject *part, GMimeStream *stream_out)\n {\n-    GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);\n     GMimeStream *stream_filter = NULL;\n     GMimeDataWrapper *wrapper;\n     const char *charset;\n \n     charset = g_mime_object_get_content_type_parameter (part, \"charset\");\n \n-    if (stream_stdout) {\n-\tg_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);\n-\tstream_filter = g_mime_stream_filter_new(stream_stdout);\n+    if (stream_out) {\n+\tstream_filter = g_mime_stream_filter_new(stream_out);\n \tg_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),\n \t\t\t\t g_mime_filter_crlf_new(FALSE, FALSE));\n         if (charset) {\n@@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)\n \tg_mime_data_wrapper_write_to_stream (wrapper, stream_filter);\n     if (stream_filter)\n \tg_object_unref(stream_filter);\n-    if (stream_stdout)\n-\tg_object_unref(stream_stdout);\n }\n \n static void\n-show_part (GMimeObject *part, int *part_count)\n+format_part_text (GMimeObject *part, int *part_count)\n {\n     GMimeContentDisposition *disposition;\n     GMimeContentType *content_type;\n+    GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);\n+\n+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);\n \n     disposition = g_mime_object_get_content_disposition (part);\n     if (disposition &&\n@@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)\n \tif (g_mime_content_type_is_type (content_type, \"text\", \"*\") &&\n \t    !g_mime_content_type_is_type (content_type, \"text\", \"html\"))\n \t{\n-\t    show_part_content (part);\n+\t    show_part_content (part, stream_stdout);\n \t}\n \n \tprintf (\"\\fattachment}\\n\");\n \n+\tif (stream_stdout)\n+\t    g_object_unref(stream_stdout);\n+\n \treturn;\n     }\n \n@@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)\n     if (g_mime_content_type_is_type (content_type, \"text\", \"*\") &&\n \t!g_mime_content_type_is_type (content_type, \"text\", \"html\"))\n     {\n-\tshow_part_content (part);\n+\tshow_part_content (part, stream_stdout);\n     }\n     else\n     {\n@@ -144,57 +274,93 @@ show_part (GMimeObject *part, int *part_count)\n     }\n \n     printf (\"\\fpart}\\n\");\n+\n+    if (stream_stdout)\n+\tg_object_unref(stream_stdout);\n }\n \n static void\n-show_message (void *ctx, notmuch_message_t *message, int indent)\n+format_part_json (GMimeObject *part, int *part_count)\n {\n-    const char *headers[] = {\n-\t\"Subject\", \"From\", \"To\", \"Cc\", \"Bcc\", \"Date\"\n-    };\n-    const char *name, *value;\n-    unsigned int i;\n+    GMimeContentType *content_type;\n+    GMimeContentDisposition *disposition;\n+    void *ctx = talloc_new (NULL);\n+    GMimeStream *stream_memory = g_mime_stream_mem_new ();\n+    GByteArray *part_content;\n \n-    printf (\"\\fmessage{ id:%s depth:%d match:%d filename:%s\\n\",\n-\t    notmuch_message_get_message_id (message),\n-\t    indent,\n-\t    notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),\n-\t    notmuch_message_get_filename (message));\n+    content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));\n \n-    printf (\"\\fheader{\\n\");\n+    if (*part_count > 1)\n+\tfputs (\", \", stdout);\n \n-    printf (\"%s\\n\", _get_one_line_summary (ctx, message));\n+    printf (\"{\\\"id\\\": %d, \\\"content-type\\\": %s\",\n+\t    *part_count,\n+\t    json_quote_str (ctx, g_mime_content_type_to_string (content_type)));\n \n-    for (i = 0; i < ARRAY_SIZE (headers); i++) {\n-\tname = headers[i];\n-\tvalue = notmuch_message_get_header (message, name);\n-\tif (value)\n-\t    printf (\"%s: %s\\n\", name, value);\n+    disposition = g_mime_object_get_content_disposition (part);\n+    if (disposition &&\n+\tstrcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)\n+    {\n+\tconst char *filename = g_mime_part_get_filename (GMIME_PART (part));\n+\n+\tprintf (\", \\\"filename\\\": %s\", json_quote_str (ctx, filename));\n     }\n \n-    printf (\"\\fheader}\\n\");\n-    printf (\"\\fbody{\\n\");\n+    if (g_mime_content_type_is_type (content_type, \"text\", \"*\") &&\n+\t!g_mime_content_type_is_type (content_type, \"text\", \"html\"))\n+    {\n+\tshow_part_content (part, stream_memory);\n+\tpart_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));\n+\n+\tprintf (\", \\\"content\\\": %s\", json_quote_str (ctx, (char *) part_content->data));\n+    }\n+\n+    fputs (\"}\", stdout);\n+\n+    talloc_free (ctx);\n+    if (stream_memory)\n+\tg_object_unref (stream_memory);\n+}\n+\n+static void\n+show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)\n+{\n+    fputs (format->message_start, stdout);\n+    format->message(ctx, message, indent);\n \n-    show_message_body (notmuch_message_get_filename (message), show_part);\n+    fputs (format->header_start, stdout);\n+    format->header(ctx, message);\n+    fputs (format->header_end, stdout);\n \n-    printf (\"\\fbody}\\n\");\n+    fputs (format->body_start, stdout);\n+    show_message_body (notmuch_message_get_filename (message), format->part);\n+    fputs (format->body_end, stdout);\n \n-    printf (\"\\fmessage}\\n\");\n+    fputs (format->message_end, stdout);\n }\n \n \n static void\n-show_messages (void *ctx, notmuch_messages_t *messages, int indent,\n+show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,\n \t       notmuch_bool_t entire_thread)\n {\n     notmuch_message_t *message;\n     notmuch_bool_t match;\n+    int first_set = 1;\n     int next_indent;\n \n+    fputs (format->message_set_start, stdout);\n+\n     for (;\n \t notmuch_messages_has_more (messages);\n \t notmuch_messages_advance (messages))\n     {\n+\tif (!first_set)\n+\t    fputs (format->message_set_sep, stdout);\n+\tfirst_set = 0;\n+\n+\tfputs (format->message_set_start, stdout);\n+\n \tmessage = notmuch_messages_get (messages);\n \n \tmatch = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);\n@@ -202,15 +368,21 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent,\n \tnext_indent = indent;\n \n \tif (match || entire_thread) {\n-\t    show_message (ctx, message, indent);\n+\t    show_message (ctx, format, message, indent);\n \t    next_indent = indent + 1;\n+\n+\t    fputs (format->message_set_sep, stdout);\n \t}\n \n-\tshow_messages (ctx, notmuch_message_get_replies (message),\n+\tshow_messages (ctx, format, notmuch_message_get_replies (message),\n \t\t       next_indent, entire_thread);\n \n \tnotmuch_message_destroy (message);\n+\n+\tfputs (format->message_set_end, stdout);\n     }\n+\n+    fputs (format->message_set_end, stdout);\n }\n \n int\n@@ -223,15 +395,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n     notmuch_thread_t *thread;\n     notmuch_messages_t *messages;\n     char *query_string;\n+    char *opt;\n+    const show_format_t *format = &format_text;\n     int entire_thread = 0;\n     int i;\n+    int first_toplevel = 1;\n \n     for (i = 0; i < argc && argv[i][0] == '-'; i++) {\n \tif (strcmp (argv[i], \"--\") == 0) {\n \t    i++;\n \t    break;\n \t}\n-        if (strcmp(argv[i], \"--entire-thread\") == 0) {\n+\tif (STRNCMP_LITERAL (argv[i], \"--output=\") == 0) {\n+\t    opt = argv[i] + sizeof (\"--output=\") - 1;\n+\t    if (strcmp (opt, \"text\") == 0) {\n+\t\tformat = &format_text;\n+\t    } else if (strcmp (opt, \"json\") == 0) {\n+\t\tformat = &format_json;\n+\t\tentire_thread = 1;\n+\t    } else {\n+\t\tfprintf (stderr, \"Invalid value for --output: %s\\n\", opt);\n+\t\treturn 1;\n+\t    }\n+\t} else if (STRNCMP_LITERAL (argv[i], \"--entire-thread\") == 0) {\n \t    entire_thread = 1;\n \t} else {\n \t    fprintf (stderr, \"Unrecognized option: %s\\n\", argv[i]);\n@@ -268,6 +454,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \treturn 1;\n     }\n \n+    fputs (format->message_set_start, stdout);\n+\n     for (threads = notmuch_query_search_threads (query);\n \t notmuch_threads_has_more (threads);\n \t notmuch_threads_advance (threads))\n@@ -280,11 +468,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \t    INTERNAL_ERROR (\"Thread %s has no toplevel messages.\\n\",\n \t\t\t    notmuch_thread_get_thread_id (thread));\n \n-\tshow_messages (ctx, messages, 0, entire_thread);\n+\tif (!first_toplevel)\n+\t    fputs (format->message_set_sep, stdout);\n+\tfirst_toplevel = 0;\n+\n+\tshow_messages (ctx, format, messages, 0, entire_thread);\n \n \tnotmuch_thread_destroy (thread);\n+\n     }\n \n+    fputs (format->message_set_end, stdout);\n+\n     notmuch_query_destroy (query);\n     notmuch_database_close (notmuch);\n \ndiff --git a/notmuch.c b/notmuch.c\nindex 2ac8a59..aa2fc12 100644\n--- a/notmuch.c\n+++ b/notmuch.c\n@@ -162,6 +162,11 @@ command_t commands[] = {\n       \"\\n\"\n       \"\\t\\tSupported options for search include:\\n\"\n       \"\\n\"\n+      \"\\t\\t--output=(json|text)\\n\"\n+      \"\\n\"\n+      \"\\t\\t\\tPresents the results in either JSON or plain-text\\n\"\n+      \"\\t\\t\\tformat, which is the default.\\n\"\n+      \"\\n\"\n       \"\\t\\t--sort=(newest-first|oldest-first)\\n\"\n       \"\\n\"\n       \"\\t\\t\\tPresent results in either chronological order\\n\"\n@@ -186,13 +191,18 @@ command_t commands[] = {\n       \"\\t\\t\\tall messages in the same thread as any matched\\n\"\n       \"\\t\\t\\tmessage will be displayed.\\n\"\n       \"\\n\"\n-      \"\\t\\tThe output format is plain-text, with all text-content\\n\"\n-      \"\\t\\tMIME parts decoded. Various components in the output,\\n\"\n-      \"\\t\\t('message', 'header', 'body', 'attachment', and MIME 'part')\\n\"\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\"\n-      \"\\t\\tthe marker, and then either an opening or closing brace,\\n\"\n-      \"\\t\\t'{' or '}' to either open or close the component.\\n\"\n+      \"\\t\\t--output=(json|text)\\n\"\n+      \"\\n\"\n+      \"\\t\\t\\tPresents the results in either JSON or plain-text\\n\"\n+      \"\\t\\t\\tformat, which is the default.\\n\"\n+      \"\\n\"\n+      \"\\t\\tThe plain-text has all text-content MIME parts decoded.\\n\"\n+      \"\\t\\tVarious components in the output, ('message', 'header',\\n\"\n+      \"\\t\\t'body', 'attachment', and MIME 'part') are delimited by\\n\"\n+      \"\\t\\teasily-parsed markers. Each marker consists of a Control-L\\n\"\n+      \"\\t\\tcharacter (ASCII decimal 12), the name of the marker, and\\n\"\n+      \"\\t\\tthen either an opening or closing brace, '{' or '}' to\\n\"\n+      \"\\t\\teither open or close the component.\\n\"\n       \"\\n\"\n       \"\\t\\tA common use of \\\"notmuch show\\\" is to display a single\\n\"\n       \"\\t\\tthread of email messages. For this, use a search term of\\n\"\ndiff --git a/show-message.c b/show-message.c\nindex 784981b..05ced9c 100644\n--- a/show-message.c\n+++ b/show-message.c\n@@ -26,8 +26,6 @@ static void\n show_message_part (GMimeObject *part, int *part_count,\n \t\t   void (*show_part) (GMimeObject *part, int *part_count))\n {\n-    *part_count = *part_count + 1;\n-\n     if (GMIME_IS_MULTIPART (part)) {\n \tGMimeMultipart *multipart = GMIME_MULTIPART (part);\n \tint i;\n@@ -56,6 +54,8 @@ show_message_part (GMimeObject *part, int *part_count,\n \treturn;\n     }\n \n+    *part_count = *part_count + 1;\n+\n     (*show_part) (part, part_count);\n }\n-- \nScott Robinson | http://quadhome.com/\n\nQ: Why are my replies five sentences or less?\nA: http://five.sentenc.es/\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n"}]}, [[{"id": "1261141195-5469-1-git-send-email-david at tethera.net", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261141237.25871.pivot.cs.unb.ca", "headers": {"Subject": "[notmuch] [PATCH] Add an \"--output=(json|text|)\" command-line option to both notmuch-search and notmuch-show.", "From": "david at tethera.net", "To": "notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Fri, 18 Dec 2009 08:59:55 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "From: Scott Robinson <scott at quadhome.com>\n\nIn the case of notmuch-show, \"--output=json\" also implies\n\"--entire-thread\" as the thread structure is implicit in the emitted\ndocument tree.\n\nAs a coincidence to the implementation, multipart message ID numbers are\nnow incremented with each part printed. This changes the previous\nsemantics, which were unclear and not necessary related to the actual\nordering of the message parts.\n\nEdited-By: David Bremner <david at tethera.net>\nReviewed-By: David Bremner <david at tethera.net>\n---\n\nIt took me a little work to apply Scott's patch, so rather than asking\nhim to resend it from git-send-email, I am just sending. I hope no-one\nis offended (much).\n\nOther than manually extracting the patch from the output of notmuch\nshow (for me the message arrived base64 encoded), I deleted trailing\nwhitespace on line 465. \n\nIt compiles, it doesn't seem to screw up the original output, and at\nleast in a few tests, it generates parseable json. Yay!.\n\nI'm thinking that the patch I sent out last night to only dump message\nids could be reworked to use the framework of this patch.  I also\nthink it would be reasonably simple to add an --output=mbox option,\nfor archiving and so on.\n\n Makefile.local   |    3 +-\n json.c           |   73 ++++++++++++++\n notmuch-client.h |    3 +\n notmuch-search.c |  163 +++++++++++++++++++++++++++++---\n notmuch-show.c   |  275 ++++++++++++++++++++++++++++++++++++++++++++++--------\n notmuch.c        |   24 ++++--\n show-message.c   |    4 +-\n 7 files changed, 481 insertions(+), 64 deletions(-)\n create mode 100644 json.c\n\ndiff --git a/Makefile.local b/Makefile.local\nindex 933ff4c..53b474b 100644\n--- a/Makefile.local\n+++ b/Makefile.local\n@@ -18,7 +18,8 @@ notmuch_client_srcs =\t\t\\\n \tnotmuch-tag.c\t\t\\\n \tnotmuch-time.c\t\t\\\n \tquery-string.c\t\t\\\n-\tshow-message.c\n+\tshow-message.c\t\t\\\n+\tjson.c\n \n notmuch_client_modules = $(notmuch_client_srcs:.c=.o)\n notmuch: $(notmuch_client_modules) lib/notmuch.a\ndiff --git a/json.c b/json.c\nnew file mode 100644\nindex 0000000..ee563d6\n--- /dev/null\n+++ b/json.c\n@@ -0,0 +1,73 @@\n+/* notmuch - Not much of an email program, (just index and search)\n+ *\n+ * Copyright  2009 Carl Worth\n+ * Copyright  2009 Keith Packard\n+ *\n+ * This program is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation, either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * along with this program.  If not, see http://www.gnu.org/licenses/ .\n+ *\n+ * Authors: Carl Worth <cworth at cworth.org>\n+ *\t    Keith Packard <keithp at keithp.com>\n+ */\n+\n+#include \"notmuch-client.h\"\n+\n+/*\n+ * json_quote_str derived from cJSON's print_string_ptr,\n+ * Copyright (c) 2009 Dave Gamble\n+ */\n+\n+char *\n+json_quote_str(const void *ctx, const char *str)\n+{\n+    const char *ptr;\n+    char *ptr2;\n+    char *out;\n+    int len = 0;\n+\n+    if (!str)\n+\treturn NULL;\n+\n+    for (ptr = str; *ptr; len++, ptr++) {\n+\tif (*ptr < 32 || *ptr == '\\\"' || *ptr == '\\\\')\n+\t    len++;\n+    }\n+\n+    out = talloc_array (ctx, char, len + 3);\n+\n+    ptr = str;\n+    ptr2 = out;\n+\n+    *ptr2++ = '\\\"';\n+    while (*ptr) {\n+\t    if (*ptr > 31 && *ptr != '\\\"' && *ptr != '\\\\') {\n+\t\t*ptr2++ = *ptr++;\n+\t    } else {\n+\t\t*ptr2++ = '\\\\';\n+\t\tswitch (*ptr++) {\n+\t\t    case '\\\"':\t*ptr2++ = '\\\"';\tbreak;\n+\t\t    case '\\\\':\t*ptr2++ = '\\\\';\tbreak;\n+\t\t    case '\\b':\t*ptr2++ = 'b';\tbreak;\n+\t\t    case '\\f':\t*ptr2++ = 'f';\tbreak;\n+\t\t    case '\\n':\t*ptr2++ = 'n';\tbreak;\n+\t\t    case '\\r':\t*ptr2++ = 'r';\tbreak;\n+\t\t    case '\\t':\t*ptr2++ = 't';\tbreak;\n+\t\t    default:\t ptr2--;\tbreak;\n+\t\t}\n+\t    }\n+    }\n+    *ptr2++ = '\\\"';\n+    *ptr2++ = '\\0';\n+\n+    return out;\n+}\ndiff --git a/notmuch-client.h b/notmuch-client.h\nindex 50a30fe..7b844b9 100644\n--- a/notmuch-client.h\n+++ b/notmuch-client.h\n@@ -143,6 +143,9 @@ notmuch_status_t\n show_message_body (const char *filename,\n \t\t   void (*show_part) (GMimeObject *part, int *part_count));\n \n+char *\n+json_quote_str (const void *ctx, const char *str);\n+\n /* notmuch-config.c */\n \n typedef struct _notmuch_config notmuch_config_t;\ndiff --git a/notmuch-search.c b/notmuch-search.c\nindex dc44eb6..e243747 100644\n--- a/notmuch-search.c\n+++ b/notmuch-search.c\n@@ -20,8 +20,120 @@\n \n #include \"notmuch-client.h\"\n \n+typedef struct search_format {\n+    const char *results_start;\n+    const char *thread_start;\n+    void (*thread) (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject);\n+    const char *tag_start;\n+    const char *tag;\n+    const char *tag_sep;\n+    const char *tag_end;\n+    const char *thread_sep;\n+    const char *thread_end;\n+    const char *results_end;\n+} search_format_t;\n+\n+static void\n+format_thread_text (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject);\n+static const search_format_t format_text = {\n+    \"\",\n+\t\"\",\n+\t    format_thread_text,\n+\t    \" (\",\n+\t\t\"%s\", \" \",\n+\t    \")\", \"\",\n+\t\"\\n\",\n+    \"\",\n+};\n+\n+static void\n+format_thread_json (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject);\n+static const search_format_t format_json = {\n+    \"[\",\n+\t\"{\",\n+\t    format_thread_json,\n+\t    \"\\\"tags\\\": [\",\n+\t\t\"\\\"%s\\\"\", \", \",\n+\t    \"]\", \",\\n\",\n+\t\"}\",\n+    \"]\\n\",\n+};\n+\n+static void\n+format_thread_text (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject)\n+{\n+    printf (\"thread:%s %12s [%d/%d] %s; %s\",\n+\t    id,\n+\t    notmuch_time_relative_date (ctx, date),\n+\t    matched,\n+\t    total,\n+\t    authors,\n+\t    subject);\n+}\n+\n+static void\n+format_thread_json (const void *ctx,\n+\t\t    const char *id,\n+\t\t    const time_t date,\n+\t\t    const int matched,\n+\t\t    const int total,\n+\t\t    const char *authors,\n+\t\t    const char *subject)\n+{\n+    struct tm *tm;\n+    char timestamp[40];\n+    void *ctx_quote = talloc_new (ctx);\n+\n+    tm = gmtime (&date);\n+    if (tm == NULL)\n+\tINTERNAL_ERROR (\"gmtime failed on thread %s.\", id);\n+\n+    if (strftime (timestamp, sizeof (timestamp), \"%s\", tm) == 0)\n+\tINTERNAL_ERROR (\"strftime failed on thread %s.\", id);\n+\n+    printf (\"\\\"id\\\": %s,\\n\"\n+\t    \"\\\"timestamp\\\": %s,\\n\"\n+\t    \"\\\"matched\\\": %d,\\n\"\n+\t    \"\\\"total\\\": %d,\\n\"\n+\t    \"\\\"authors\\\": %s,\\n\"\n+\t    \"\\\"subject\\\": %s,\\n\",\n+\t    json_quote_str (ctx_quote, id),\n+\t    timestamp,\n+\t    matched,\n+\t    total,\n+\t    json_quote_str (ctx_quote, authors),\n+\t    json_quote_str (ctx_quote, subject));\n+\n+    talloc_free (ctx_quote);\n+}\n+\n static void\n do_search_threads (const void *ctx,\n+\t\t   const search_format_t *format,\n \t\t   notmuch_query_t *query,\n \t\t   notmuch_sort_t sort)\n {\n@@ -29,7 +141,9 @@ do_search_threads (const void *ctx,\n     notmuch_threads_t *threads;\n     notmuch_tags_t *tags;\n     time_t date;\n-    const char *relative_date;\n+    int first_thread = 1;\n+\n+    fputs (format->results_start, stdout);\n \n     for (threads = notmuch_query_search_threads (query);\n \t notmuch_threads_has_more (threads);\n@@ -37,6 +151,9 @@ do_search_threads (const void *ctx,\n     {\n \tint first_tag = 1;\n \n+\tif (! first_thread)\n+\t    fputs (format->thread_sep, stdout);\n+\n \tthread = notmuch_threads_get (threads);\n \n \tif (sort == NOTMUCH_SORT_OLDEST_FIRST)\n@@ -44,30 +161,37 @@ do_search_threads (const void *ctx,\n \telse\n \t    date = notmuch_thread_get_newest_date (thread);\n \n-\trelative_date = notmuch_time_relative_date (ctx, date);\n+\tfputs (format->thread_start, stdout);\n+\n+\tformat->thread (ctx,\n+\t\t\tnotmuch_thread_get_thread_id (thread),\n+\t\t\tdate,\n+\t\t\tnotmuch_thread_get_matched_messages (thread),\n+\t\t\tnotmuch_thread_get_total_messages (thread),\n+\t\t\tnotmuch_thread_get_authors (thread),\n+\t\t\tnotmuch_thread_get_subject (thread));\n \n-\tprintf (\"thread:%s %12s [%d/%d] %s; %s\",\n-\t\tnotmuch_thread_get_thread_id (thread),\n-\t\trelative_date,\n-\t\tnotmuch_thread_get_matched_messages (thread),\n-\t\tnotmuch_thread_get_total_messages (thread),\n-\t\tnotmuch_thread_get_authors (thread),\n-\t\tnotmuch_thread_get_subject (thread));\n+\tfputs (format->tag_start, stdout);\n \n-\tprintf (\" (\");\n \tfor (tags = notmuch_thread_get_tags (thread);\n \t     notmuch_tags_has_more (tags);\n \t     notmuch_tags_advance (tags))\n \t{\n \t    if (! first_tag)\n-\t\tprintf (\" \");\n-\t    printf (\"%s\", notmuch_tags_get (tags));\n+\t\tfputs (format->tag_sep, stdout);\n+\t    printf (format->tag, notmuch_tags_get (tags));\n \t    first_tag = 0;\n \t}\n-\tprintf (\")\\n\");\n+\n+\tfputs (format->tag_end, stdout);\n+\tfputs (format->thread_end, stdout);\n+\n+\tfirst_thread = 0;\n \n \tnotmuch_thread_destroy (thread);\n     }\n+\n+    fputs (format->results_end, stdout);\n }\n \n int\n@@ -79,6 +203,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n     char *query_str;\n     char *opt;\n     notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;\n+    const search_format_t *format = &format_text;\n     int i;\n \n     for (i = 0; i < argc && argv[i][0] == '-'; i++) {\n@@ -96,6 +221,16 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n \t\tfprintf (stderr, \"Invalid value for --sort: %s\\n\", opt);\n \t\treturn 1;\n \t    }\n+\t} else if (STRNCMP_LITERAL (argv[i], \"--output=\") == 0) {\n+\t    opt = argv[i] + sizeof (\"--output=\") - 1;\n+\t    if (strcmp (opt, \"text\") == 0) {\n+\t\tformat = &format_text;\n+\t    } else if (strcmp (opt, \"json\") == 0) {\n+\t\tformat = &format_json;\n+\t    } else {\n+\t\tfprintf (stderr, \"Invalid value for --output: %s\\n\", opt);\n+\t\treturn 1;\n+\t    }\n \t} else {\n \t    fprintf (stderr, \"Unrecognized option: %s\\n\", argv[i]);\n \t    return 1;\n@@ -132,7 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n \n     notmuch_query_set_sort (query, sort);\n \n-    do_search_threads (ctx, query, sort);\n+    do_search_threads (ctx, format, query, sort);\n \n     notmuch_query_destroy (query);\n     notmuch_database_close (notmuch);\ndiff --git a/notmuch-show.c b/notmuch-show.c\nindex 376aacd..b5b3eba 100644\n--- a/notmuch-show.c\n+++ b/notmuch-show.c\n@@ -20,8 +20,65 @@\n \n #include \"notmuch-client.h\"\n \n+typedef struct show_format {\n+    const char *message_set_start;\n+    const char *message_start;\n+    void (*message) (const void *ctx,\n+\t\t     notmuch_message_t *message,\n+\t\t     int indent);\n+    const char *header_start;\n+    void (*header) (const void *ctx,\n+\t\t    notmuch_message_t *message);\n+    const char *header_end;\n+    const char *body_start;\n+    void (*part) (GMimeObject *part,\n+\t\t  int *part_count);\n+    const char *body_end;\n+    const char *message_end;\n+    const char *message_set_sep;\n+    const char *message_set_end;\n+} show_format_t;\n+\n+static void\n+format_message_text (unused (const void *ctx),\n+\t\t     notmuch_message_t *message,\n+\t\t     int indent);\n+static void\n+format_headers_text (const void *ctx,\n+\t\t     notmuch_message_t *message);\n+static void\n+format_part_text (GMimeObject *part,\n+\t\t  int *part_count);\n+static const show_format_t format_text = {\n+    \"\",\n+\t\"\\fmessage{ \", format_message_text,\n+\t    \"\\fheader{\\n\", format_headers_text, \"\\fheader}\\n\",\n+\t    \"\\fbody{\\n\", format_part_text, \"\\fbody}\\n\",\n+\t\"\\fmessage}\\n\", \"\",\n+    \"\"\n+};\n+\n+static void\n+format_message_json (const void *ctx,\n+\t\t     notmuch_message_t *message,\n+\t\t     unused (int indent));\n+static void\n+format_headers_json (const void *ctx,\n+\t\t     notmuch_message_t *message);\n+static void\n+format_part_json (GMimeObject *part,\n+\t\t  int *part_count);\n+static const show_format_t format_json = {\n+    \"[\",\n+\t\"{\", format_message_json,\n+\t    \", \\\"headers\\\": {\", format_headers_json, \"}\",\n+\t    \", \\\"body\\\": [\", format_part_json, \"]\",\n+\t\"}\", \", \",\n+    \"]\"\n+};\n+\n static const char *\n-_get_tags_as_string (void *ctx, notmuch_message_t *message)\n+_get_tags_as_string (const void *ctx, notmuch_message_t *message)\n {\n     notmuch_tags_t *tags;\n     int first = 1;\n@@ -48,7 +105,7 @@ _get_tags_as_string (void *ctx, notmuch_message_t *message)\n \n /* Get a nice, single-line summary of message. */\n static const char *\n-_get_one_line_summary (void *ctx, notmuch_message_t *message)\n+_get_one_line_summary (const void *ctx, notmuch_message_t *message)\n {\n     const char *from;\n     time_t date;\n@@ -67,18 +124,87 @@ _get_one_line_summary (void *ctx, notmuch_message_t *message)\n }\n \n static void\n-show_part_content (GMimeObject *part)\n+format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)\n+{\n+    printf (\"id:%s depth:%d match:%d filename:%s\\n\",\n+\t    notmuch_message_get_message_id (message),\n+\t    indent,\n+\t    notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),\n+\t    notmuch_message_get_filename (message));\n+}\n+\n+static void\n+format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))\n+{\n+    void *ctx_quote = talloc_new (ctx);\n+\n+    printf (\"\\\"id\\\": %s, \\\"match\\\": %s, \\\"filename\\\": %s\",\n+\t    json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),\n+\t    notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? \"true\" : \"false\",\n+\t    json_quote_str (ctx_quote, notmuch_message_get_filename (message)));\n+\n+    talloc_free (ctx_quote);\n+}\n+\n+static void\n+format_headers_text (const void *ctx, notmuch_message_t *message)\n+{\n+    const char *headers[] = {\n+\t\"Subject\", \"From\", \"To\", \"Cc\", \"Bcc\", \"Date\"\n+    };\n+    const char *name, *value;\n+    unsigned int i;\n+\n+    printf (\"%s\\n\", _get_one_line_summary (ctx, message));\n+\n+    for (i = 0; i < ARRAY_SIZE (headers); i++) {\n+\tname = headers[i];\n+\tvalue = notmuch_message_get_header (message, name);\n+\tif (value)\n+\t    printf (\"%s: %s\\n\", name, value);\n+    }\n+}\n+\n+static void\n+format_headers_json (const void *ctx, notmuch_message_t *message)\n+{\n+    const char *headers[] = {\n+\t\"Subject\", \"From\", \"To\", \"Cc\", \"Bcc\", \"Date\"\n+    };\n+    const char *name, *value;\n+    unsigned int i;\n+    int first_header = 1;\n+    void *ctx_quote = talloc_new (ctx);\n+\n+    for (i = 0; i < ARRAY_SIZE (headers); i++) {\n+\tname = headers[i];\n+\tvalue = notmuch_message_get_header (message, name);\n+\tif (value)\n+\t{\n+\t    if (!first_header)\n+\t\tfputs (\", \", stdout);\n+\t    first_header = 0;\n+\n+\t    printf (\"%s: %s\",\n+\t\t    json_quote_str (ctx_quote, name),\n+\t\t    json_quote_str (ctx_quote, value));\n+\t}\n+    }\n+\n+    talloc_free (ctx_quote);\n+}\n+\n+static void\n+show_part_content (GMimeObject *part, GMimeStream *stream_out)\n {\n-    GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);\n     GMimeStream *stream_filter = NULL;\n     GMimeDataWrapper *wrapper;\n     const char *charset;\n \n     charset = g_mime_object_get_content_type_parameter (part, \"charset\");\n \n-    if (stream_stdout) {\n-\tg_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);\n-\tstream_filter = g_mime_stream_filter_new(stream_stdout);\n+    if (stream_out) {\n+\tstream_filter = g_mime_stream_filter_new(stream_out);\n \tg_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),\n \t\t\t\t g_mime_filter_crlf_new(FALSE, FALSE));\n         if (charset) {\n@@ -92,15 +218,16 @@ show_part_content (GMimeObject *part)\n \tg_mime_data_wrapper_write_to_stream (wrapper, stream_filter);\n     if (stream_filter)\n \tg_object_unref(stream_filter);\n-    if (stream_stdout)\n-\tg_object_unref(stream_stdout);\n }\n \n static void\n-show_part (GMimeObject *part, int *part_count)\n+format_part_text (GMimeObject *part, int *part_count)\n {\n     GMimeContentDisposition *disposition;\n     GMimeContentType *content_type;\n+    GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);\n+\n+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);\n \n     disposition = g_mime_object_get_content_disposition (part);\n     if (disposition &&\n@@ -118,11 +245,14 @@ show_part (GMimeObject *part, int *part_count)\n \tif (g_mime_content_type_is_type (content_type, \"text\", \"*\") &&\n \t    !g_mime_content_type_is_type (content_type, \"text\", \"html\"))\n \t{\n-\t    show_part_content (part);\n+\t    show_part_content (part, stream_stdout);\n \t}\n \n \tprintf (\"\\fattachment}\\n\");\n \n+\tif (stream_stdout)\n+\t    g_object_unref(stream_stdout);\n+\n \treturn;\n     }\n \n@@ -135,7 +265,7 @@ show_part (GMimeObject *part, int *part_count)\n     if (g_mime_content_type_is_type (content_type, \"text\", \"*\") &&\n \t!g_mime_content_type_is_type (content_type, \"text\", \"html\"))\n     {\n-\tshow_part_content (part);\n+\tshow_part_content (part, stream_stdout);\n     }\n     else\n     {\n@@ -144,57 +274,93 @@ show_part (GMimeObject *part, int *part_count)\n     }\n \n     printf (\"\\fpart}\\n\");\n+\n+    if (stream_stdout)\n+\tg_object_unref(stream_stdout);\n }\n \n static void\n-show_message (void *ctx, notmuch_message_t *message, int indent)\n+format_part_json (GMimeObject *part, int *part_count)\n {\n-    const char *headers[] = {\n-\t\"Subject\", \"From\", \"To\", \"Cc\", \"Bcc\", \"Date\"\n-    };\n-    const char *name, *value;\n-    unsigned int i;\n+    GMimeContentType *content_type;\n+    GMimeContentDisposition *disposition;\n+    void *ctx = talloc_new (NULL);\n+    GMimeStream *stream_memory = g_mime_stream_mem_new ();\n+    GByteArray *part_content;\n \n-    printf (\"\\fmessage{ id:%s depth:%d match:%d filename:%s\\n\",\n-\t    notmuch_message_get_message_id (message),\n-\t    indent,\n-\t    notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),\n-\t    notmuch_message_get_filename (message));\n+    content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));\n \n-    printf (\"\\fheader{\\n\");\n+    if (*part_count > 1)\n+\tfputs (\", \", stdout);\n \n-    printf (\"%s\\n\", _get_one_line_summary (ctx, message));\n+    printf (\"{\\\"id\\\": %d, \\\"content-type\\\": %s\",\n+\t    *part_count,\n+\t    json_quote_str (ctx, g_mime_content_type_to_string (content_type)));\n \n-    for (i = 0; i < ARRAY_SIZE (headers); i++) {\n-\tname = headers[i];\n-\tvalue = notmuch_message_get_header (message, name);\n-\tif (value)\n-\t    printf (\"%s: %s\\n\", name, value);\n+    disposition = g_mime_object_get_content_disposition (part);\n+    if (disposition &&\n+\tstrcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)\n+    {\n+\tconst char *filename = g_mime_part_get_filename (GMIME_PART (part));\n+\n+\tprintf (\", \\\"filename\\\": %s\", json_quote_str (ctx, filename));\n     }\n \n-    printf (\"\\fheader}\\n\");\n-    printf (\"\\fbody{\\n\");\n+    if (g_mime_content_type_is_type (content_type, \"text\", \"*\") &&\n+\t!g_mime_content_type_is_type (content_type, \"text\", \"html\"))\n+    {\n+\tshow_part_content (part, stream_memory);\n+\tpart_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));\n+\n+\tprintf (\", \\\"content\\\": %s\", json_quote_str (ctx, (char *) part_content->data));\n+    }\n+\n+    fputs (\"}\", stdout);\n+\n+    talloc_free (ctx);\n+    if (stream_memory)\n+\tg_object_unref (stream_memory);\n+}\n+\n+static void\n+show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)\n+{\n+    fputs (format->message_start, stdout);\n+    format->message(ctx, message, indent);\n \n-    show_message_body (notmuch_message_get_filename (message), show_part);\n+    fputs (format->header_start, stdout);\n+    format->header(ctx, message);\n+    fputs (format->header_end, stdout);\n \n-    printf (\"\\fbody}\\n\");\n+    fputs (format->body_start, stdout);\n+    show_message_body (notmuch_message_get_filename (message), format->part);\n+    fputs (format->body_end, stdout);\n \n-    printf (\"\\fmessage}\\n\");\n+    fputs (format->message_end, stdout);\n }\n \n \n static void\n-show_messages (void *ctx, notmuch_messages_t *messages, int indent,\n+show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,\n \t       notmuch_bool_t entire_thread)\n {\n     notmuch_message_t *message;\n     notmuch_bool_t match;\n+    int first_set = 1;\n     int next_indent;\n \n+    fputs (format->message_set_start, stdout);\n+\n     for (;\n \t notmuch_messages_has_more (messages);\n \t notmuch_messages_advance (messages))\n     {\n+\tif (!first_set)\n+\t    fputs (format->message_set_sep, stdout);\n+\tfirst_set = 0;\n+\n+\tfputs (format->message_set_start, stdout);\n+\n \tmessage = notmuch_messages_get (messages);\n \n \tmatch = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);\n@@ -202,15 +368,21 @@ show_messages (void *ctx, notmuch_messages_t *messages, int indent,\n \tnext_indent = indent;\n \n \tif (match || entire_thread) {\n-\t    show_message (ctx, message, indent);\n+\t    show_message (ctx, format, message, indent);\n \t    next_indent = indent + 1;\n+\n+\t    fputs (format->message_set_sep, stdout);\n \t}\n \n-\tshow_messages (ctx, notmuch_message_get_replies (message),\n+\tshow_messages (ctx, format, notmuch_message_get_replies (message),\n \t\t       next_indent, entire_thread);\n \n \tnotmuch_message_destroy (message);\n+\n+\tfputs (format->message_set_end, stdout);\n     }\n+\n+    fputs (format->message_set_end, stdout);\n }\n \n int\n@@ -223,15 +395,29 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n     notmuch_thread_t *thread;\n     notmuch_messages_t *messages;\n     char *query_string;\n+    char *opt;\n+    const show_format_t *format = &format_text;\n     int entire_thread = 0;\n     int i;\n+    int first_toplevel = 1;\n \n     for (i = 0; i < argc && argv[i][0] == '-'; i++) {\n \tif (strcmp (argv[i], \"--\") == 0) {\n \t    i++;\n \t    break;\n \t}\n-        if (strcmp(argv[i], \"--entire-thread\") == 0) {\n+\tif (STRNCMP_LITERAL (argv[i], \"--output=\") == 0) {\n+\t    opt = argv[i] + sizeof (\"--output=\") - 1;\n+\t    if (strcmp (opt, \"text\") == 0) {\n+\t\tformat = &format_text;\n+\t    } else if (strcmp (opt, \"json\") == 0) {\n+\t\tformat = &format_json;\n+\t\tentire_thread = 1;\n+\t    } else {\n+\t\tfprintf (stderr, \"Invalid value for --output: %s\\n\", opt);\n+\t\treturn 1;\n+\t    }\n+\t} else if (STRNCMP_LITERAL (argv[i], \"--entire-thread\") == 0) {\n \t    entire_thread = 1;\n \t} else {\n \t    fprintf (stderr, \"Unrecognized option: %s\\n\", argv[i]);\n@@ -268,6 +454,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \treturn 1;\n     }\n \n+    fputs (format->message_set_start, stdout);\n+\n     for (threads = notmuch_query_search_threads (query);\n \t notmuch_threads_has_more (threads);\n \t notmuch_threads_advance (threads))\n@@ -280,11 +468,18 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \t    INTERNAL_ERROR (\"Thread %s has no toplevel messages.\\n\",\n \t\t\t    notmuch_thread_get_thread_id (thread));\n \n-\tshow_messages (ctx, messages, 0, entire_thread);\n+\tif (!first_toplevel)\n+\t    fputs (format->message_set_sep, stdout);\n+\tfirst_toplevel = 0;\n+\n+\tshow_messages (ctx, format, messages, 0, entire_thread);\n \n \tnotmuch_thread_destroy (thread);\n+\n     }\n \n+    fputs (format->message_set_end, stdout);\n+\n     notmuch_query_destroy (query);\n     notmuch_database_close (notmuch);\n \ndiff --git a/notmuch.c b/notmuch.c\nindex 2ac8a59..aa2fc12 100644\n--- a/notmuch.c\n+++ b/notmuch.c\n@@ -162,6 +162,11 @@ command_t commands[] = {\n       \"\\n\"\n       \"\\t\\tSupported options for search include:\\n\"\n       \"\\n\"\n+      \"\\t\\t--output=(json|text)\\n\"\n+      \"\\n\"\n+      \"\\t\\t\\tPresents the results in either JSON or plain-text\\n\"\n+      \"\\t\\t\\tformat, which is the default.\\n\"\n+      \"\\n\"\n       \"\\t\\t--sort=(newest-first|oldest-first)\\n\"\n       \"\\n\"\n       \"\\t\\t\\tPresent results in either chronological order\\n\"\n@@ -186,13 +191,18 @@ command_t commands[] = {\n       \"\\t\\t\\tall messages in the same thread as any matched\\n\"\n       \"\\t\\t\\tmessage will be displayed.\\n\"\n       \"\\n\"\n-      \"\\t\\tThe output format is plain-text, with all text-content\\n\"\n-      \"\\t\\tMIME parts decoded. Various components in the output,\\n\"\n-      \"\\t\\t('message', 'header', 'body', 'attachment', and MIME 'part')\\n\"\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\"\n-      \"\\t\\tthe marker, and then either an opening or closing brace,\\n\"\n-      \"\\t\\t'{' or '}' to either open or close the component.\\n\"\n+      \"\\t\\t--output=(json|text)\\n\"\n+      \"\\n\"\n+      \"\\t\\t\\tPresents the results in either JSON or plain-text\\n\"\n+      \"\\t\\t\\tformat, which is the default.\\n\"\n+      \"\\n\"\n+      \"\\t\\tThe plain-text has all text-content MIME parts decoded.\\n\"\n+      \"\\t\\tVarious components in the output, ('message', 'header',\\n\"\n+      \"\\t\\t'body', 'attachment', and MIME 'part') are delimited by\\n\"\n+      \"\\t\\teasily-parsed markers. Each marker consists of a Control-L\\n\"\n+      \"\\t\\tcharacter (ASCII decimal 12), the name of the marker, and\\n\"\n+      \"\\t\\tthen either an opening or closing brace, '{' or '}' to\\n\"\n+      \"\\t\\teither open or close the component.\\n\"\n       \"\\n\"\n       \"\\t\\tA common use of \\\"notmuch show\\\" is to display a single\\n\"\n       \"\\t\\tthread of email messages. For this, use a search term of\\n\"\ndiff --git a/show-message.c b/show-message.c\nindex 784981b..05ced9c 100644\n--- a/show-message.c\n+++ b/show-message.c\n@@ -26,8 +26,6 @@ static void\n show_message_part (GMimeObject *part, int *part_count,\n \t\t   void (*show_part) (GMimeObject *part, int *part_count))\n {\n-    *part_count = *part_count + 1;\n-\n     if (GMIME_IS_MULTIPART (part)) {\n \tGMimeMultipart *multipart = GMIME_MULTIPART (part);\n \tint i;\n@@ -56,6 +54,8 @@ show_message_part (GMimeObject *part, int *part_count,\n \treturn;\n     }\n \n+    *part_count = *part_count + 1;\n+\n     (*show_part) (part, part_count);\n }\n \n-- \n1.6.5.3\n\n"}, {"id": 2, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n<notmuch@notmuchmail.org>;\tFri, 18 Dec 2009 05:00:16 -0800 (PST)"}]}, [[{"id": "87iqc4yyo8.fsf at yoom.home.cworth.org", "match": false, "filename": "/home/bremner/Maildir/.quarantine/new/1261157631.26884.pivot.cs.unb.ca", "headers": {"Subject": "Re: [notmuch] [PATCH] Add an \"--output=(json|text|)\" command-line option to both notmuch-search and notmuch-show.", "From": "Carl Worth <cworth at cworth.org>", "To": "david at tethera.net, notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Fri, 18 Dec 2009 09:33:43 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Fri, 18 Dec 2009 08:59:55 -0400, david at tethera.net wrote:\n> It took me a little work to apply Scott's patch, so rather than asking\n> him to resend it from git-send-email, I am just sending. I hope no-one\n> is offended (much).\n\nI think that's great! Collaboration is what this is all about.\n\n> I'm thinking that the patch I sent out last night to only dump message\n> ids could be reworked to use the framework of this patch.  I also\n> think it would be reasonably simple to add an --output=mbox option,\n> for archiving and so on.\n\nI think that selecting *what* to emit is orthogonal from selecting *how*\nto format that output. See some ideas in the TODO file, (where I\nproposed --for and --format options for these). Having a way to do mbox\noutput for export would indeed be very nice.\n\n-Carl\nt show_format_t format_text = {\n+    \"\",\n+\t\"\\fmessage{ \", format_message_text,\n+\t    \"\\fheader{\\n\", format_headers_text, \"\\fheader}\\n\",\n+\t    \"\\fbody{\\n\", format_part_text, \"\\fbody}\\n\",\n+\t\"\\fmessage}\\n\", \"\",\n+    \"\"\n+};\n+\n+static voi"}, {"id": 2, "content-type": "application/pgp-signature"}]}, [[{"id": "1261161342-sup-2335 at lisa", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261161941.27175.pivot.cs.unb.ca", "headers": {"Subject": "Re: [notmuch] [PATCH] Add an \"--output=(json|text|)\" command-line option to both notmuch-search and notmuch-show.", "From": "Scott Robinson <scott at quadhome.com>", "To": "notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Fri, 18 Dec 2009 10:45:17 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "Excerpts from Carl Worth's message of Fri Dec 18 09:33:43 -0800 2009:\n> On Fri, 18 Dec 2009 08:59:55 -0400, david at tethera.net wrote:\n> > It took me a little work to apply Scott's patch, so rather than asking\n> > him to resend it from git-send-email, I am just sending. I hope no-one\n> > is offended (much).\n> \n> I think that's great! Collaboration is what this is all about.\n\nMe too!\n\nI've never used git-send-email. I'll give it a whirl on my next patch.\n\n> > I'm thinking that the patch I sent out last night to only dump message\n> > ids could be reworked to use the framework of this patch.  I also\n> > think it would be reasonably simple to add an --output=mbox option,\n> > for archiving and so on.\n> \n> I think that selecting *what* to emit is orthogonal from selecting *how*\n> to format that output. See some ideas in the TODO file, (where I\n> proposed --for and --format options for these). Having a way to do mbox\n> output for export would indeed be very nice.\n\nHaha! I originally used \"--format\" and changed for some reason that escapes me\nnow.\n\nImplementing an \"mbox\" formatted output in the current logic wouldn't be\narchive perfect. The message body is emitted on a per-part basis.\n\nWhat I would do is change the semantics of format->body to be called from\nshow_message. Then the text and json parts would point at the original\nimplementation passing off their per-part function pointers. And, a new mbox\nimplementation would just dump the full message body.\n-- \nScott Robinson | http://quadhome.com/\n\nQ: Why are my replies five sentences or less?\nA: http://five.sentenc.es/\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\ng_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));\n+\n+\tprintf (\", \\\"content\\\": %s\", json_quote_str (ctx, (char *) part_content->data));\n+    }\n+\n+    fputs (\"}\", stdout);\n+\n+    talloc_free (ctx);\n+    if (stream_memory)\n+\tg_object_unref (stream_memory);\n+}\n+\n+static void\n+show_message (void *ctx, const A"}]}, []], [{"id": "871virsstp.fsf at pivot.cs.unb.ca", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261183015.28394.pivot.cs.unb.ca", "headers": {"Subject": "Re: [notmuch] [PATCH] Add an \"--output=(json|text|)\" command-line option to both notmuch-search and notmuch-show.", "From": "David Bremner <david at tethera.net>", "To": "Carl Worth <cworth at cworth.org>, notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Fri, 18 Dec 2009 20:36:34 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Fri, 18 Dec 2009 09:33:43 -0800, Carl Worth <cworth at cworth.org> wrote:\n\n> I think that selecting *what* to emit is orthogonal from selecting *how*\n> to format that output. \n\nI can see that point of view.\n\n> See some ideas in the TODO file, (where I proposed --for and --format\n> options for these).\n\nIt's a detail, but could you choose two names that are not substrings of\neach other?  Eventually we do want tab completion on the command line to\nwork :).  Also, \"search --for tags foo\" suggests to me that\nsearching for tags matching foo.  What about using --output for that?\nOne thing that is not completely clear to me at this point is what the\ndifference is between \n\n    notmuch search --for messages  search-terms\n\nand \n\n    notmuch show search-terms\n\nDavid\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, []], [{"id": "1261234524-25522-1-git-send-email-david at tethera.net", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261234552.13784.pivot.cs.unb.ca", "headers": {"Subject": "[notmuch] Prototype of --show option to control what is shown.", "From": "david at tethera.net", "To": "notmuch at notmuchmail.org", "Cc": "", "Bcc": "", "Date": "Sat, 19 Dec 2009 10:55:21 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "Here is a set of 3 patches to implement (some) output control.  \n\n[PATCH 1/3] rename option to select output format to --format from --output.\n[PATCH 2/3] notmuch-show.c: make calls to format functions conditional\n\nThe first two are essentially suggestions for Scott.  In particular the first \nis not needed at all for this little experiment.\n\n[PATCH 3/3] notmuch-show.c: prototype tabular output format, with output control\n\nThe third implements a new \"tabular\" output format, and shows how it\nmight be conditionally controlled.  At the moment the only\npossibilities to output are message-id, match flag, and filename.\nThis is mainly a matter of laziness.  I didn't want to go too crazy\nbefore Scott's reached some final(ish) version.\n\n\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\nring (void *ctx, notmuch_message_t *message)\\n \\n /* Get a nice, single-line summary of message. */\\n static const char *\\n-_get_one_line_summary (vIQ"}]}, [[{"id": "1261234524-25522-2-git-send-email-david at tethera.net", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261234562.13803.pivot.cs.unb.ca", "headers": {"Subject": "[notmuch] [PATCH 1/3] rename option to select output format to --format from --output.", "From": "david at tethera.net", "To": "notmuch at notmuchmail.org", "Cc": "David Bremner <bremner at unb.ca>", "Bcc": "", "Date": "Sat, 19 Dec 2009 10:55:22 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "From: David Bremner <bremner at unb.ca>\n\n---\n notmuch-search.c |    6 +++---\n notmuch-show.c   |    6 +++---\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/notmuch-search.c b/notmuch-search.c\nindex e243747..482c6e8 100644\n--- a/notmuch-search.c\n+++ b/notmuch-search.c\n@@ -221,14 +221,14 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\n \t\tfprintf (stderr, \"Invalid value for --sort: %s\\n\", opt);\n \t\treturn 1;\n \t    }\n-\t} else if (STRNCMP_LITERAL (argv[i], \"--output=\") == 0) {\n-\t    opt = argv[i] + sizeof (\"--output=\") - 1;\n+\t} else if (STRNCMP_LITERAL (argv[i], \"--format=\") == 0) {\n+\t    opt = argv[i] + sizeof (\"--format=\") - 1;\n \t    if (strcmp (opt, \"text\") == 0) {\n \t\tformat = &format_text;\n \t    } else if (strcmp (opt, \"json\") == 0) {\n \t\tformat = &format_json;\n \t    } else {\n-\t\tfprintf (stderr, \"Invalid value for --output: %s\\n\", opt);\n+\t\tfprintf (stderr, \"Invalid value for --format: %s\\n\", opt);\n \t\treturn 1;\n \t    }\n \t} else {\ndiff --git a/notmuch-show.c b/notmuch-show.c\nindex b5b3eba..b6e3f44 100644\n--- a/notmuch-show.c\n+++ b/notmuch-show.c\n@@ -406,15 +406,15 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \t    i++;\n \t    break;\n \t}\n-\tif (STRNCMP_LITERAL (argv[i], \"--output=\") == 0) {\n-\t    opt = argv[i] + sizeof (\"--output=\") - 1;\n+\tif (STRNCMP_LITERAL (argv[i], \"--format=\") == 0) {\n+\t    opt = argv[i] + sizeof (\"--format=\") - 1;\n \t    if (strcmp (opt, \"text\") == 0) {\n \t\tformat = &format_text;\n \t    } else if (strcmp (opt, \"json\") == 0) {\n \t\tformat = &format_json;\n \t\tentire_thread = 1;\n \t    } else {\n-\t\tfprintf (stderr, \"Invalid value for --output: %s\\n\", opt);\n+\t\tfprintf (stderr, \"Invalid value for --format: %s\\n\", opt);\n \t\treturn 1;\n \t    }\n \t} else if (STRNCMP_LITERAL (argv[i], \"--entire-thread\") == 0) {\n-- \n1.6.5.3\n\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, [[{"id": "1261234524-25522-3-git-send-email-david at tethera.net", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261234570.13817.pivot.cs.unb.ca", "headers": {"Subject": "[notmuch] [PATCH 2/3] notmuch-show.c: make calls to format functions conditional", "From": "david at tethera.net", "To": "notmuch at notmuchmail.org", "Cc": "David Bremner <bremner at unb.ca>", "Bcc": "", "Date": "Sat, 19 Dec 2009 10:55:23 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "From: David Bremner <bremner at unb.ca>\n\nThis makes it easier to define minimal formats without defining several\ndummy functions that do nothing.\n---\n notmuch-show.c |   13 +++++++++----\n 1 files changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/notmuch-show.c b/notmuch-show.c\nindex b6e3f44..51aa87d 100644\n--- a/notmuch-show.c\n+++ b/notmuch-show.c\n@@ -326,14 +326,17 @@ static void\n show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)\n {\n     fputs (format->message_start, stdout);\n-    format->message(ctx, message, indent);\n+    if (format->message)\n+\tformat->message(ctx, message, indent);\n \n     fputs (format->header_start, stdout);\n-    format->header(ctx, message);\n+    if (format->header) \n+\tformat->header(ctx, message);\n     fputs (format->header_end, stdout);\n \n     fputs (format->body_start, stdout);\n-    show_message_body (notmuch_message_get_filename (message), format->part);\n+    if (format->part) \n+\tshow_message_body (notmuch_message_get_filename (message), format->part);\n     fputs (format->body_end, stdout);\n \n     fputs (format->message_end, stdout);\n@@ -408,7 +411,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \t}\n \tif (STRNCMP_LITERAL (argv[i], \"--format=\") == 0) {\n \t    opt = argv[i] + sizeof (\"--format=\") - 1;\n-\t    if (strcmp (opt, \"text\") == 0) {\n+\t    if (strcmp (opt, \"tabular\") == 0) {\n+\t\tformat = &format_tabular;\n+\t    } else if (strcmp (opt, \"text\") == 0) {\n \t\tformat = &format_text;\n \t    } else if (strcmp (opt, \"json\") == 0) {\n \t\tformat = &format_json;\n-- \n1.6.5.3\n\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, [[{"id": "1261234524-25522-4-git-send-email-david at tethera.net", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261234575.13823.pivot.cs.unb.ca", "headers": {"Subject": "[notmuch] [PATCH 3/3] notmuch-show.c: prototype tabular output format, with output control", "From": "david at tethera.net", "To": "notmuch at notmuchmail.org", "Cc": "David Bremner <bremner at unb.ca>", "Bcc": "", "Date": "Sat, 19 Dec 2009 10:55:24 -0400"}, "body": [{"id": 1, "content-type": "text/plain", "content": "From: David Bremner <bremner at unb.ca>\n\nCurrently this only outputs the information from the \"message header\";\ni.e. the part before the rfc2822 header or body.\n\nAdding this required adding an extra parameter, currently unused, to\nformat_message_text and format_message_json. Also the struct\ndefinition is changed to match the new function prototypes.\n\nAn int (enum) is used as a mask, each bit corresponds to some part to\nbe shown or not shown.  This enum will need to be extended as more\nthings are controllable via --show.\n---\n notmuch-show.c |   89 ++++++++++++++++++++++++++++++++++++++++++++++++-------\n 1 files changed, 77 insertions(+), 12 deletions(-)\n\ndiff --git a/notmuch-show.c b/notmuch-show.c\nindex 51aa87d..99a4d3c 100644\n--- a/notmuch-show.c\n+++ b/notmuch-show.c\n@@ -20,10 +20,18 @@\n \n #include \"notmuch-client.h\"\n \n+/* These should be powers of 2 */\n+typedef enum {SHOW_MESSAGE_ID = 1, \n+\t      SHOW_MATCH = 2, \n+\t      SHOW_FILENAME = 4\n+} show_mask_t;\n+\n+static const show_mask_t SHOW_ALL = ~0;\n+\n typedef struct show_format {\n     const char *message_set_start;\n     const char *message_start;\n-    void (*message) (const void *ctx,\n+    void (*message) (const void *ctx, show_mask_t show_mask,\n \t\t     notmuch_message_t *message,\n \t\t     int indent);\n     const char *header_start;\n@@ -40,7 +48,7 @@ typedef struct show_format {\n } show_format_t;\n \n static void\n-format_message_text (unused (const void *ctx),\n+format_message_text (unused (const void *ctx), show_mask_t show_mask,\n \t\t     notmuch_message_t *message,\n \t\t     int indent);\n static void\n@@ -59,7 +67,21 @@ static const show_format_t format_text = {\n };\n \n static void\n-format_message_json (const void *ctx,\n+format_message_tabular (unused (const void *ctx), show_mask_t show_mask,\n+\t\t     notmuch_message_t *message,\n+\t\t     int indent);\n+\n+static const show_format_t format_tabular = {\n+    \"\",\t \n+    \"\", format_message_tabular, \n+    \"\", NULL, \"\", \n+    \"\", NULL, \"\",    \n+    \"\\n\", \"\", \n+    \"\"\n+};\n+\n+static void\n+format_message_json (const void *ctx, show_mask_t show_mask,\n \t\t     notmuch_message_t *message,\n \t\t     unused (int indent));\n static void\n@@ -124,7 +146,30 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)\n }\n \n static void\n-format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)\n+format_message_tabular (unused (const void *ctx), show_mask_t show_mask,\n+\t\t\tnotmuch_message_t *message, \n+\t\t\tunused(int indent))\n+{\n+    int count=0;\n+\n+    if (show_mask & SHOW_MESSAGE_ID) {\n+\tfputs ( notmuch_message_get_message_id (message), stdout);\n+\tcount++;\n+    }\n+\n+    if (show_mask & SHOW_MATCH) \n+\tprintf (\"%s%d\",\tcount++ ? \"\\t\" : \"\", \n+\t\tnotmuch_message_get_flag (message, \n+\t\t\t\t\t  NOTMUCH_MESSAGE_FLAG_MATCH));\n+\n+    if (show_mask & SHOW_FILENAME) \n+\tprintf(\"%s%s\", count++ ? \"\\t\" : \"\", \n+\t       notmuch_message_get_filename (message));\n+}\n+\n+static void\n+format_message_text (unused (const void *ctx), unused(show_mask_t show_mask),\n+\t\t     notmuch_message_t *message, int indent)\n {\n     printf (\"id:%s depth:%d match:%d filename:%s\\n\",\n \t    notmuch_message_get_message_id (message),\n@@ -134,7 +179,8 @@ format_message_text (unused (const void *ctx), notmuch_message_t *message, int i\n }\n \n static void\n-format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))\n+format_message_json (const void *ctx, unused(show_mask_t show_mask),\n+\t\t     notmuch_message_t *message, unused (int indent))\n {\n     void *ctx_quote = talloc_new (ctx);\n \n@@ -323,11 +369,12 @@ format_part_json (GMimeObject *part, int *part_count)\n }\n \n static void\n-show_message (void *ctx, const show_format_t *format, notmuch_message_t *message, int indent)\n+show_message (void *ctx, const show_format_t *format, show_mask_t show_mask,\n+\t      notmuch_message_t *message, int indent)\n {\n     fputs (format->message_start, stdout);\n     if (format->message)\n-\tformat->message(ctx, message, indent);\n+\tformat->message(ctx, show_mask, message, indent);\n \n     fputs (format->header_start, stdout);\n     if (format->header) \n@@ -344,8 +391,8 @@ show_message (void *ctx, const show_format_t *format, notmuch_message_t *message\n \n \n static void\n-show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messages, int indent,\n-\t       notmuch_bool_t entire_thread)\n+show_messages (void *ctx, const show_format_t *format,  show_mask_t show_mask, \n+\t       notmuch_messages_t *messages, int indent, notmuch_bool_t entire_thread)\n {\n     notmuch_message_t *message;\n     notmuch_bool_t match;\n@@ -371,13 +418,14 @@ show_messages (void *ctx, const show_format_t *format, notmuch_messages_t *messa\n \tnext_indent = indent;\n \n \tif (match || entire_thread) {\n-\t    show_message (ctx, format, message, indent);\n+\t    show_message (ctx, format, show_mask, message, indent);\n \t    next_indent = indent + 1;\n \n \t    fputs (format->message_set_sep, stdout);\n \t}\n \n-\tshow_messages (ctx, format, notmuch_message_get_replies (message),\n+\tshow_messages (ctx, format, show_mask, \n+\t\t       notmuch_message_get_replies (message),\n \t\t       next_indent, entire_thread);\n \n \tnotmuch_message_destroy (message);\n@@ -403,6 +451,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n     int entire_thread = 0;\n     int i;\n     int first_toplevel = 1;\n+    show_mask_t show_mask=SHOW_ALL;\n \n     for (i = 0; i < argc && argv[i][0] == '-'; i++) {\n \tif (strcmp (argv[i], \"--\") == 0) {\n@@ -422,6 +471,22 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \t\tfprintf (stderr, \"Invalid value for --format: %s\\n\", opt);\n \t\treturn 1;\n \t    }\n+\t} else if (STRNCMP_LITERAL (argv[i], \"--show=\") == 0) {\n+\t    char *keyword=NULL;\n+\t    show_mask=0;\n+\t    opt = argv[i] + sizeof (\"--show=\") - 1;\n+\t    while ((keyword=strsep (&opt,\", \"))) {\n+\t\tif (strcmp (keyword, \"message-id\") == 0) {\n+\t\t    show_mask |= SHOW_MESSAGE_ID;\n+\t\t} else if (strcmp (keyword, \"match\") == 0) {\n+\t\t    show_mask |= SHOW_MATCH;\n+\t\t} else if (strcmp (keyword, \"filename\") == 0) {\n+\t\t    show_mask |= SHOW_FILENAME;\n+\t\t} else {\n+\t\t    fprintf (stderr, \"Invalid value for --show: %s\\n\", opt);\n+\t\t    return 1;\n+\t\t}\n+\t    }\n \t} else if (STRNCMP_LITERAL (argv[i], \"--entire-thread\") == 0) {\n \t    entire_thread = 1;\n \t} else {\n@@ -477,7 +542,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\n \t    fputs (format->message_set_sep, stdout);\n \tfirst_toplevel = 0;\n \n-\tshow_messages (ctx, format, messages, 0, entire_thread);\n+\tshow_messages (ctx, format, show_mask, messages, 0, entire_thread);\n \n \tnotmuch_thread_destroy (thread);\n \n-- \n1.6.5.3\n\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, []]]]]]]]]]]], [{"id": "87k4wkyyro.fsf at yoom.home.cworth.org", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261157514.26874.pivot.cs.unb.ca", "headers": {"Subject": "Re: [notmuch] [PATCH] JSON output for notmuch-search and notmuch-show.", "From": "Carl Worth <cworth at cworth.org>", "To": "Scott Robinson <scott at quadhome.com>, notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Fri, 18 Dec 2009 09:31:39 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Thu, 17 Dec 2009 21:33:54 -0800, Scott Robinson <scott at quadhome.com> wrote:\n> I took an earlier suggestion and didn't use cJSON, instead writing custom code\n> for emitting the new format.\n\nNice! I have a few comments below.\n\n> Added an \"--output=(json|text|)\" command-line option to both\n> notmuch-search and notmuch-show.\n\nI don't know why, but I think I'd prefer --format for the name here.\n\n> In the case of notmuch-show, \"--output=json\" also implies\n> \"--entire-thread\" as the thread structure is implicit in the emitted\n> document tree.\n\nIt looks like the new documentation is missing that point, (and the man\npage in notmuch.1 is missing an update as well).\n\n> As a coincidence to the implementation, multipart message ID numbers are\n> now incremented with each part printed. This changes the previous\n> semantics, which were unclear and not necessary related to the actual\n> ordering of the message parts.\n\nThat's just fine. The old numbering semantics were quite bizarre and\nnothing I wanted to set it stone.\n\n-Carl\nspeech.net>)\tid 1NM0j8-0003ai-8s\tfor bremner at pivot.cs.unb.ca; Sat, 19 Dec 2009 10:56:14 -0400"}, {"id": 2, "content-type": "application/pgp-signature"}, {"id": 3, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\nU0\t-To"}]}, [[{"id": "1261161923-sup-6874 at lisa", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/new/1261162067.27184.pivot.cs.unb.ca", "headers": {"Subject": "Re: [notmuch] [PATCH] JSON output for notmuch-search and notmuch-show.", "From": "Scott Robinson <scott at quadhome.com>", "To": "notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Fri, 18 Dec 2009 10:47:33 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "Excerpts from Carl Worth's message of Fri Dec 18 09:31:39 -0800 2009:\n> [...]\n> I don't know why, but I think I'd prefer --format for the name here.\n\nACK\n\n> [...]\n> It looks like the new documentation is missing that point, (and the man\n> page in notmuch.1 is missing an update as well).\n\nACK\n\n> [...]\n> That's just fine. The old numbering semantics were quite bizarre and\n> nothing I wanted to set it stone.\n\nCool. :-)\n\nResubmit a full patch, or submit another one on top of it?\n-- \nScott Robinson | http://quadhome.com/\n\nQ: Why are my replies five sentences or less?\nA: http://five.sentenc.es/\n_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n\n"}]}, []]]]]]]]]]]]], [[{"id": "87r5r3joth.fsf at anar.kanru.info", "match": true, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260428520.9594.pivot.cs.unb.ca:2,", "headers": {"Subject": "[notmuch] Patch for xapian defect #250", "From": "Kan-Ru Chen <kanru at kanru.info>", "To": "notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Thu, 10 Dec 2009 15:00:42 +0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "\nThe termlist is already sorted, so this is the patch trying to minimize\nthe modification of database as suggested in the comment and Carl's\nTODO file.\n\nMy poor profiling shows not much, but some improvement.\n\n*Before*\n \n% time notmuch tag +test id:hfntnu+gotv at eGroups.com   \nMOD_PLISTS: 368\nnotmuch tag +test id:hfntnu+gotv at eGroups.com  0.05s user 0.03s system 11% cpu 0.673 total\n\n% time notmuch tag -test id:hfntnu+gotv at eGroups.com   \nMOD_PLISTS: 368\nnotmuch tag -test id:hfntnu+gotv at eGroups.com  0.06s user 0.01s system 10% cpu 0.681 total\n \n*After*\n \n% time notmuch tag +test id:hfntnu+gotv at eGroups.com                \nMOD_PLIST: 1\nnotmuch tag +test id:hfntnu+gotv at eGroups.com  0.01s user 0.02s system 6% cpu 0.436 total\n\n% time notmuch tag -test id:hfntnu+gotv at eGroups.com                \nMOD_PLIST: 1\nnotmuch tag -test id:hfntnu+gotv at eGroups.com  0.01s user 0.01s system 5% cpu 0.383 total\n\n\n% time notmuch tag +test tag:notmuch\nnotmuch tag +test tag:notmuch  1.71s user 0.03s system 65% cpu 2.632 total\n\n% time notmuch tag -test tag:notmuch\nnotmuch tag -test tag:notmuch  1.61s user 0.02s system 73% cpu 2.204 total\n\n% notmuch count tag:notmuch\n682\n\n--- flint_database.cc\t2009-12-08 13:34:24.790284881 +0800\n+++ flint_database.cc\t2009-12-10 14:22:14.493653956 +0800\n@@ -1188,7 +1188,7 @@\n \n \ttermlist.next();\n \twhile (!termlist.at_end()) {\n-\t    string tname = termlist.get_termname();\n+            string tname = termlist.get_termname();\n \t    position_table.delete_positionlist(did, tname);\n \t    termcount wdf = termlist.get_wdf();\n \n@@ -1278,20 +1278,50 @@\n \t}\n   \n \tif (!modifying || document.internal->terms_modified()) {\n-\t    // FIXME - in the case where there is overlap between the new\n-\t    // termlist and the old termlist, it would be better to compare the\n-\t    // two lists, and make the minimum set of modifications required.\n-\t    // This would lead to smaller changesets for replication, and\n-\t    // probably be faster overall.\n-\n-\t    // First, add entries to remove the postings in the underlying record.\n \t    Xapian::Internal::RefCntPtr<const FlintWritableDatabase> ptrtothis(this);\n \t    FlintTermList termlist(ptrtothis, did);\n+            Xapian::TermIterator term = document.termlist_begin();\n+\t    Xapian::TermIterator term_end = document.termlist_end();\n+            flint_doclen_t new_doclen = termlist.get_doclength();\n+            string old_tname, new_tname;\n+            \n+            total_length -= new_doclen;\n+            \n+            termlist.next();\n+            while (true) {\n+              bool identical = false;\n+              int cmp;\n+              if (termlist.at_end() && term == term_end)\n+                break;\n+              if (!termlist.at_end() && term != term_end) {\n+                old_tname = termlist.get_termname();\n+                new_tname = *term;\n+                cmp = old_tname.compare(new_tname);\n+\n+                // Check postlist to see whether they are identical\n+                if (cmp == 0) {\n+                  int new_count = term.positionlist_count();\n+                  int old_count = termlist.positionlist_count();\n+                  if (old_count == new_count) {\n+                    PositionIterator it = term.positionlist_begin();\n+                    PositionIterator it_end = term.positionlist_end();\n+                    PositionIterator old = termlist.positionlist_begin();\n+                    if (equal(it, it_end, old))\n+                      identical = true;\n+                  }\n+                }\n+              } else if (termlist.at_end()) {\n+                cmp = 2;\n+                new_tname = *term;\n+              } else {\n+                cmp = -2;\n+                old_tname = termlist.get_termname();\n+              }\n \n-\t    termlist.next();\n-\t    while (!termlist.at_end()) {\n-\t\tstring tname = termlist.get_termname();\n+              if (cmp < 0) {\n+                const string& tname = old_tname;\n \t\ttermcount wdf = termlist.get_wdf();\n+                new_doclen -= wdf;\n \n \t\tmap<string, pair<termcount_diff, termcount_diff> >::iterator i;\n \t\ti = freq_deltas.find(tname);\n@@ -1318,58 +1348,62 @@\n \t\t    // Modifying a document we added/modified since the last flush.\n \t\t    k->second = make_pair('D', 0u);\n \t\t}\n-\n-\t\ttermlist.next();\n-\t    }\n-\n-\t    total_length -= termlist.get_doclength();\n-\n-\t    flint_doclen_t new_doclen = 0;\n-\t    Xapian::TermIterator term = document.termlist_begin();\n-\t    Xapian::TermIterator term_end = document.termlist_end();\n-\t    for ( ; term != term_end; ++term) {\n-\t\t// Calculate the new document length\n+              } else if (!identical) {\n+                const string& tname = new_tname;\n \t\ttermcount wdf = term.get_wdf();\n-\t\tnew_doclen += wdf;\n-\n-\t\tstring tname = *term;\n-\t\tif (tname.size() > MAX_SAFE_TERM_LENGTH)\n-\t\t    throw Xapian::InvalidArgumentError(\"Term too long (> \"STRINGIZE(MAX_SAFE_TERM_LENGTH)\"): \" + tname);\n-\t\tmap<string, pair<termcount_diff, termcount_diff> >::iterator i;\n-\t\ti = freq_deltas.find(tname);\n-\t\tif (i == freq_deltas.end()) {\n-\t\t    freq_deltas.insert(make_pair(tname, make_pair(1, termcount_diff(wdf))));\n-\t\t} else {\n-\t\t    ++i->second.first;\n-\t\t    i->second.second += wdf;\n-\t\t}\n+                new_doclen += wdf;\n+                \n+                if (cmp > 0) {\n+                  if (tname.size() > MAX_SAFE_TERM_LENGTH)\n+                    throw Xapian::InvalidArgumentError(\"Term too long (> \"STRINGIZE(MAX_SAFE_TERM_LENGTH)\"): \" + tname);\n+                  map<string, pair<termcount_diff, termcount_diff> >::iterator i;\n+                  i = freq_deltas.find(tname);\n+                  if (i == freq_deltas.end()) {\n+                    freq_deltas.insert(make_pair(tname, make_pair(1, termcount_diff(wdf))));\n+                  } else {\n+                    ++i->second.first;\n+                    i->second.second += wdf;\n+                  }\n+\n+                  // Add did to tname's postlist\n+                  map<string, map<docid, pair<char, termcount> > >::iterator j;\n+                  j = mod_plists.find(tname);\n+                  if (j == mod_plists.end()) {\n+                    map<docid, pair<char, termcount> > m;\n+                    j = mod_plists.insert(make_pair(tname, m)).first;\n+                  }\n+                  map<docid, pair<char, termcount> >::iterator k;\n+                  k = j->second.find(did);\n+                  if (k != j->second.end()) {\n+                    Assert(k->second.first == 'D');\n+                    k->second.first = 'M';\n+                    k->second.second = wdf;\n+                  } else {\n+                    j->second.insert(make_pair(did, make_pair('A', wdf)));\n+                  }\n+                }\n+\n+                PositionIterator it = term.positionlist_begin();\n+                PositionIterator it_end = term.positionlist_end();\n+                if (it != it_end) {\n+                  position_table.set_positionlist(\n+                                                  did, tname, it, it_end);\n+                } else {\n+                  position_table.delete_positionlist(did, tname);\n+                }\n+              }\n+              if (termlist.at_end())\n+                ++term;\n+              else if (term == term_end)\n+                termlist.next();\n+              else {\n+                if (cmp >= 0)\n+                  ++term;\n+                if (cmp <= 0)\n+                  termlist.next();\n+              }\n+            }\n \n-\t\t// Add did to tname's postlist\n-\t\tmap<string, map<docid, pair<char, termcount> > >::iterator j;\n-\t\tj = mod_plists.find(tname);\n-\t\tif (j == mod_plists.end()) {\n-\t\t    map<docid, pair<char, termcount> > m;\n-\t\t    j = mod_plists.insert(make_pair(tname, m)).first;\n-\t\t}\n-\t\tmap<docid, pair<char, termcount> >::iterator k;\n-\t\tk = j->second.find(did);\n-\t\tif (k != j->second.end()) {\n-\t\t    Assert(k->second.first == 'D');\n-\t\t    k->second.first = 'M';\n-\t\t    k->second.second = wdf;\n-\t\t} else {\n-\t\t    j->second.insert(make_pair(did, make_pair('A', wdf)));\n-\t\t}\n-\n-\t\tPositionIterator it = term.positionlist_begin();\n-\t\tPositionIterator it_end = term.positionlist_end();\n-\t\tif (it != it_end) {\n-\t\t    position_table.set_positionlist(\n-\t\t\tdid, tname, it, it_end);\n-\t\t} else {\n-\t\t    position_table.delete_positionlist(did, tname);\n-\t\t}\n-\t    }\n \t    LOGLINE(DB, \"Calculated doclen for replacement document \" << did << \" as \" << new_doclen);\n \n \t    // Set the termlist\n\n\n-- \nKan-Ru Chen | http://kanru.info\n\nQ: Why are my replies five sentences or less?\nA: http://five.sentenc.es/\nc4f"}, {"id": 2, "content-type": "application/pgp-signature"}, {"id": 3, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\n.69) (envelope-from <kanru at kanru.info>) id 1NId1s-0001y9-3n\tfor notmuch at notmuchmail.org; Thu, 10 Dec 2009 07:01:37 +0000"}]}, [[{"id": "87638e3xgc.fsf at yoom.home.cworth.org", "match": false, "filename": "/home/bremner/Maildir/.list.notmuch/cur/1260508402.30807.pivot.cs.unb.ca:2,", "headers": {"Subject": "Re: [notmuch] Patch for xapian defect #250", "From": "Carl Worth <cworth at cworth.org>", "To": "Kan-Ru Chen <kanru at kanru.info>, notmuch <notmuch at notmuchmail.org>", "Cc": "", "Bcc": "", "Date": "Thu, 10 Dec 2009 21:13:07 -0800"}, "body": [{"id": 1, "content-type": "text/plain", "content": "On Thu, 10 Dec 2009 15:00:42 +0800, Kan-Ru Chen <kanru at kanru.info> wrote:\n> The termlist is already sorted, so this is the patch trying to minimize\n> the modification of database as suggested in the comment and Carl's\n> TODO file.\n\nFantastic, Kan-Ru!\n\n> My poor profiling shows not much, but some improvement.\n\nNow you're just understating for sake of the pun. A 5-6x performance\nimprovement looks great. And I see that as well in my testing:\n\nBefore:\n\n\t$ time notmuch tag +foo tag:sent\n\treal 3m18.067s\n\t$ time notmuch tag -foo tag:sent\n\treal 3m12.940s\n\nAfter:\n\n\t$ time notmuch tag +foo tag:sent\n        real 0m31.497s\n\t$ time notmuch tag -foo tag:sent\n\treal 0m28.722s\n\nI didn't flush the OS caches between runs, but a subsequent run of the\n\"before\" code still performed similarly slow:\n\n\t$ time notmuch tag +foo tag:sent\n\treal 3m7.172s\n\nAnd if I *had* used cold caches for every run the benefit of the patch\nwould have looked even better.\n\nAnyway, we should get this upstream to the Xapian folks straight\naway. I expect they'll want to see a patch to the chert backend as well\nas the flint backend, (but fortunately the relevant code looks very\nsimilar if not identical).\n\nThanks again,\n\n-Carl\nU%=|Kd\ffv!s"}, {"id": 2, "content-type": "application/pgp-signature"}, {"id": 3, "content-type": "text/plain", "content": "_______________________________________________\nnotmuch mailing list\nnotmuch at notmuchmail.org\nhttp://notmuchmail.org/mailman/listinfo/notmuch\ne-from <tethera.net at forward.nearlyfreespeech.net>)\tid 1NIxoe-00080n-Rn\tfor bremner at pivot.cs.unb.ca; Fri, 11 Dec 2009 01"}]}, []]]]]]


More information about the Pkg-ruby-extras-maintainers mailing list