From fb1c016cb5ec5263b24c9210c26f8e6f6cfd0942 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 16:46:15 +0200 Subject: cli: slightly refactor "notmuch reply" address scanning functions Slightly refactor "notmuch reply" recipient and user from address scanning functions in preparation for reply-to-sender feature. Add support for not adding recipients at all (just scan for user from address), and returning the number of recipients added. No externally visible functional changes. Signed-off-by: Jani Nikula --- notmuch-reply.c | 76 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/notmuch-reply.c b/notmuch-reply.c index 000f6da..a8d6a94 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -168,22 +168,29 @@ address_is_users (const char *address, notmuch_config_t *config) return 0; } -/* For each address in 'list' that is not configured as one of the - * user's addresses in 'config', add that address to 'message' as an - * address of 'type'. +/* Scan addresses in 'list'. * - * The first address encountered that *is* the user's address will be - * returned, (otherwise NULL is returned). + * If 'message' is non-NULL, then for each address in 'list' that is + * not configured as one of the user's addresses in 'config', add that + * address to 'message' as an address of 'type'. + * + * If 'user_from' is non-NULL and *user_from is NULL, *user_from will + * be set to the first address encountered in 'list' that is the + * user's address. + * + * Return the number of addresses added to 'message'. (If 'message' is + * NULL, the function returns 0 by definition.) */ -static const char * -add_recipients_for_address_list (GMimeMessage *message, - notmuch_config_t *config, - GMimeRecipientType type, - InternetAddressList *list) +static unsigned int +scan_address_list (InternetAddressList *list, + notmuch_config_t *config, + GMimeMessage *message, + GMimeRecipientType type, + const char **user_from) { InternetAddress *address; int i; - const char *ret = NULL; + unsigned int n = 0; for (i = 0; i < internet_address_list_length (list); i++) { address = internet_address_list_get_address (list, i); @@ -196,8 +203,7 @@ add_recipients_for_address_list (GMimeMessage *message, if (group_list == NULL) continue; - add_recipients_for_address_list (message, config, - type, group_list); + n += scan_address_list (group_list, config, message, type, NULL); } else { InternetAddressMailbox *mailbox; const char *name; @@ -209,40 +215,41 @@ add_recipients_for_address_list (GMimeMessage *message, addr = internet_address_mailbox_get_addr (mailbox); if (address_is_users (addr, config)) { - if (ret == NULL) - ret = addr; - } else { + if (user_from && *user_from == NULL) + *user_from = addr; + } else if (message) { g_mime_message_add_recipient (message, type, name, addr); + n++; } } } - return ret; + return n; } -/* For each address in 'recipients' that is not configured as one of - * the user's addresses in 'config', add that address to 'message' as - * an address of 'type'. +/* Scan addresses in 'recipients'. * - * The first address encountered that *is* the user's address will be - * returned, (otherwise NULL is returned). + * See the documentation of scan_address_list() above. This function + * does exactly the same, but converts 'recipients' to an + * InternetAddressList first. */ -static const char * -add_recipients_for_string (GMimeMessage *message, - notmuch_config_t *config, - GMimeRecipientType type, - const char *recipients) +static unsigned int +scan_address_string (const char *recipients, + notmuch_config_t *config, + GMimeMessage *message, + GMimeRecipientType type, + const char **user_from) { InternetAddressList *list; if (recipients == NULL) - return NULL; + return 0; list = internet_address_list_parse_string (recipients); if (list == NULL) - return NULL; + return 0; - return add_recipients_for_address_list (message, config, type, list); + return scan_address_list (list, config, message, type, user_from); } /* Does the address in the Reply-To header of 'message' already appear @@ -324,7 +331,7 @@ add_recipients_from_message (GMimeMessage *reply, } for (i = 0; i < ARRAY_SIZE (reply_to_map); i++) { - const char *addr, *recipients; + const char *recipients; recipients = notmuch_message_get_header (message, reply_to_map[i].header); @@ -332,11 +339,8 @@ add_recipients_from_message (GMimeMessage *reply, recipients = notmuch_message_get_header (message, reply_to_map[i].fallback); - addr = add_recipients_for_string (reply, config, - reply_to_map[i].recipient_type, - recipients); - if (from_addr == NULL) - from_addr = addr; + scan_address_string (recipients, config, reply, + reply_to_map[i].recipient_type, &from_addr); } return from_addr; -- cgit v1.2.3 From 0f8148e920810349df207414e40b9489dc246c37 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 16:46:16 +0200 Subject: cli: add support for replying just to the sender in "notmuch reply" Add new option --reply-to=(all|sender) to "notmuch reply" to select whether to reply to all (sender and all recipients), or just sender. Reply to all remains the default. Credits to Mark Walters for his similar earlier work where I picked up the basic idea of handling reply-to-sender in add_recipients_from_message(). All bugs are mine, though. Signed-off-by: Jani Nikula --- man/man1/notmuch-reply.1 | 28 +++++++++++++++++++----- notmuch-reply.c | 57 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index db464d8..5160ece 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -14,11 +14,13 @@ Constructs a reply template for a set of messages. To make replying to email easier, .B notmuch reply takes an existing set of messages and constructs a suitable mail -template. The Reply-to header (if any, otherwise From:) is used for -the To: address. Vales from the To: and Cc: headers are copied, but -not including any of the current user's email addresses (as configured -in primary_mail or other_email in the .notmuch\-config file) in the -recipient list +template. The Reply-to: header (if any, otherwise From:) is used for +the To: address. Unless +.BR \-\-reply-to=sender +is specified, values from the To: and Cc: headers are copied, but not +including any of the current user's email addresses (as configured in +primary_mail or other_email in the .notmuch\-config file) in the +recipient list. It also builds a suitable new subject, including Re: at the front (if not already present), and adding the message IDs of the messages being @@ -45,6 +47,22 @@ Includes subject and quoted message body. Only produces In\-Reply\-To, References, To, Cc, and Bcc headers. .RE .RE +.RS +.TP 4 +.BR \-\-reply\-to= ( all | sender ) +.RS +.TP 4 +.BR all " (default)" +Replies to all addresses. +.TP 4 +.BR sender +Replies only to the sender. If replying to user's own message +(Reply-to: or From: header is one of the user's configured email +addresses), try To:, Cc:, and Bcc: headers in this order, and copy +values from the first that contains something other than only the +user's addresses. +.RE +.RE See \fBnotmuch-search-terms\fR(7) for details of the supported syntax for . diff --git a/notmuch-reply.c b/notmuch-reply.c index a8d6a94..da3acce 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -291,15 +291,23 @@ reply_to_header_is_redundant (notmuch_message_t *message) return 0; } -/* Augments the recipients of reply from the headers of message. +/* Augment the recipients of 'reply' from the "Reply-to:", "From:", + * "To:", "Cc:", and "Bcc:" headers of 'message'. * - * If any of the user's addresses were found in these headers, the first - * of these returned, otherwise NULL is returned. + * If 'reply_all' is true, use sender and all recipients, otherwise + * scan the headers for the first that contains something other than + * the user's addresses and add the recipients from this header + * (typically this would be reply-to-sender, but also handles reply to + * user's own message in a sensible way). + * + * If any of the user's addresses were found in these headers, the + * first of these returned, otherwise NULL is returned. */ static const char * add_recipients_from_message (GMimeMessage *reply, notmuch_config_t *config, - notmuch_message_t *message) + notmuch_message_t *message, + notmuch_bool_t reply_all) { struct { const char *header; @@ -313,6 +321,7 @@ add_recipients_from_message (GMimeMessage *reply, }; const char *from_addr = NULL; unsigned int i; + unsigned int n = 0; /* Some mailing lists munge the Reply-To header despite it being A Bad * Thing, see http://www.unicom.com/pw/reply-to-harmful.html @@ -339,8 +348,24 @@ add_recipients_from_message (GMimeMessage *reply, recipients = notmuch_message_get_header (message, reply_to_map[i].fallback); - scan_address_string (recipients, config, reply, - reply_to_map[i].recipient_type, &from_addr); + n += scan_address_string (recipients, config, reply, + reply_to_map[i].recipient_type, &from_addr); + + if (!reply_all && n) { + /* Stop adding new recipients in reply-to-sender mode if + * we have added some recipient(s) above. + * + * This also handles the case of user replying to his own + * message, where reply-to/from is not a recipient. In + * this case there may be more than one recipient even if + * not replying to all. + */ + reply = NULL; + + /* From address and some recipients are enough, bail out. */ + if (from_addr) + break; + } } return from_addr; @@ -484,7 +509,8 @@ static int notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_t *query, - notmuch_show_params_t *params) + notmuch_show_params_t *params, + notmuch_bool_t reply_all) { GMimeMessage *reply; notmuch_messages_t *messages; @@ -513,7 +539,8 @@ notmuch_reply_format_default(void *ctx, g_mime_message_set_subject (reply, subject); } - from_addr = add_recipients_from_message (reply, config, message); + from_addr = add_recipients_from_message (reply, config, message, + reply_all); if (from_addr == NULL) from_addr = guess_from_received_header (config, message); @@ -562,7 +589,8 @@ static int notmuch_reply_format_headers_only(void *ctx, notmuch_config_t *config, notmuch_query_t *query, - unused (notmuch_show_params_t *params)) + unused (notmuch_show_params_t *params), + notmuch_bool_t reply_all) { GMimeMessage *reply; notmuch_messages_t *messages; @@ -602,7 +630,7 @@ notmuch_reply_format_headers_only(void *ctx, g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); - (void)add_recipients_from_message (reply, config, message); + (void)add_recipients_from_message (reply, config, message, reply_all); reply_headers = g_mime_object_to_string (GMIME_OBJECT (reply)); printf ("%s", reply_headers); @@ -629,9 +657,10 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_query_t *query; char *query_string; int opt_index, ret = 0; - int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params); + int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all); notmuch_show_params_t params = { .part = -1 }; int format = FORMAT_DEFAULT; + int reply_all = TRUE; notmuch_bool_t decrypt = FALSE; notmuch_opt_desc_t options[] = { @@ -639,6 +668,10 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT }, { "headers-only", FORMAT_HEADERS_ONLY }, { 0, 0 } } }, + { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r', + (notmuch_keyword_t []){ { "all", TRUE }, + { "sender", FALSE }, + { 0, 0 } } }, { NOTMUCH_OPT_BOOLEAN, &decrypt, "decrypt", 'd', 0 }, { 0, 0, 0, 0, 0 } }; @@ -692,7 +725,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) return 1; } - if (reply_format_func (ctx, config, query, ¶ms) != 0) + if (reply_format_func (ctx, config, query, ¶ms, reply_all) != 0) return 1; notmuch_query_destroy (query); -- cgit v1.2.3 From dc0919c9125b323306a59751a63181b67aee5b32 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 16:46:17 +0200 Subject: emacs: add support for replying just to the sender Provide reply to sender counterparts to the search and show reply functions. Add key binding 'R' to reply to sender, while keeping 'r' as reply to all, both in search and show views. Signed-off-by: Jani Nikula --- emacs/notmuch-mua.el | 9 ++++++--- emacs/notmuch-show.el | 10 ++++++++-- emacs/notmuch.el | 9 ++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 32e2e30..d8ab822 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -71,12 +71,15 @@ list." (push header message-hidden-headers))) notmuch-mua-hidden-headers)) -(defun notmuch-mua-reply (query-string &optional sender) +(defun notmuch-mua-reply (query-string &optional sender reply-all) (let (headers body (args '("reply"))) (if notmuch-show-process-crypto (setq args (append args '("--decrypt")))) + (if reply-all + (setq args (append args '("--reply-to=all"))) + (setq args (append args '("--reply-to=sender")))) (setq args (append args (list query-string))) ;; This make assumptions about the output of `notmuch reply', but ;; really only that the headers come first followed by a blank @@ -218,13 +221,13 @@ the From: address first." (notmuch-mua-forward-message)) (notmuch-mua-forward-message))) -(defun notmuch-mua-new-reply (query-string &optional prompt-for-sender) +(defun notmuch-mua-new-reply (query-string &optional prompt-for-sender reply-all) "Invoke the notmuch reply window." (interactive "P") (let ((sender (when prompt-for-sender (notmuch-mua-prompt-for-sender)))) - (notmuch-mua-reply query-string sender))) + (notmuch-mua-reply query-string sender reply-all))) (defun notmuch-mua-send-and-exit (&optional arg) (interactive "P") diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 034db87..9031b82 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -934,6 +934,7 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map "m" 'notmuch-mua-new-mail) (define-key map "f" 'notmuch-show-forward-message) (define-key map "r" 'notmuch-show-reply) + (define-key map "R" 'notmuch-show-reply-sender) (define-key map "|" 'notmuch-show-pipe-message) (define-key map "w" 'notmuch-show-save-attachments) (define-key map "V" 'notmuch-show-view-raw-message) @@ -1238,9 +1239,14 @@ any effects from previous calls to (notmuch-show-previous-message))))) (defun notmuch-show-reply (&optional prompt-for-sender) - "Reply to the current message." + "Reply to the sender and all recipients of the current message." (interactive "P") - (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender)) + (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender t)) + +(defun notmuch-show-reply-sender (&optional prompt-for-sender) + "Reply to the sender of the current message." + (interactive "P") + (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender nil)) (defun notmuch-show-forward-message (&optional prompt-for-sender) "Forward the current message." diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 1e61775..9ac2888 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -214,6 +214,7 @@ For a mouse binding, return nil." (define-key map "p" 'notmuch-search-previous-thread) (define-key map "n" 'notmuch-search-next-thread) (define-key map "r" 'notmuch-search-reply-to-thread) + (define-key map "R" 'notmuch-search-reply-to-thread-sender) (define-key map "m" 'notmuch-mua-new-mail) (define-key map "s" 'notmuch-search) (define-key map "o" 'notmuch-search-toggle-order) @@ -448,10 +449,16 @@ Complete list of currently available key bindings: (message "End of search results.")))) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender) + "Begin composing a reply-all to the entire current thread in a new buffer." + (interactive "P") + (let ((message-id (notmuch-search-find-thread-id))) + (notmuch-mua-new-reply message-id prompt-for-sender t))) + +(defun notmuch-search-reply-to-thread-sender (&optional prompt-for-sender) "Begin composing a reply to the entire current thread in a new buffer." (interactive "P") (let ((message-id (notmuch-search-find-thread-id))) - (notmuch-mua-new-reply message-id prompt-for-sender))) + (notmuch-mua-new-reply message-id prompt-for-sender nil))) (defun notmuch-call-notmuch-process (&rest args) "Synchronously invoke \"notmuch\" with the given list of arguments. -- cgit v1.2.3 From f02b475fa781bb5df3358c73213e7633a99f016e Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 16:46:18 +0200 Subject: emacs: bind 'r' to reply-to-sender and 'R' to reply-to-all Change the default reply key bindings, making 'r' reply-to-sender and 'R' reply-to-all. Signed-off-by: Jani Nikula --- emacs/notmuch-show.el | 4 ++-- emacs/notmuch.el | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 9031b82..03c1f6b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -933,8 +933,8 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map "s" 'notmuch-search) (define-key map "m" 'notmuch-mua-new-mail) (define-key map "f" 'notmuch-show-forward-message) - (define-key map "r" 'notmuch-show-reply) - (define-key map "R" 'notmuch-show-reply-sender) + (define-key map "r" 'notmuch-show-reply-sender) + (define-key map "R" 'notmuch-show-reply) (define-key map "|" 'notmuch-show-pipe-message) (define-key map "w" 'notmuch-show-save-attachments) (define-key map "V" 'notmuch-show-view-raw-message) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 9ac2888..d952c41 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -213,8 +213,8 @@ For a mouse binding, return nil." (define-key map ">" 'notmuch-search-last-thread) (define-key map "p" 'notmuch-search-previous-thread) (define-key map "n" 'notmuch-search-next-thread) - (define-key map "r" 'notmuch-search-reply-to-thread) - (define-key map "R" 'notmuch-search-reply-to-thread-sender) + (define-key map "r" 'notmuch-search-reply-to-thread-sender) + (define-key map "R" 'notmuch-search-reply-to-thread) (define-key map "m" 'notmuch-mua-new-mail) (define-key map "s" 'notmuch-search) (define-key map "o" 'notmuch-search-toggle-order) -- cgit v1.2.3 From 15ea8625d1895a376c18c23845a774f32eb7aece Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 14 Jan 2012 16:46:19 +0200 Subject: test: add tests for "notmuch reply" --reply-to=sender --- test/notmuch-test | 1 + test/reply-to-sender | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100755 test/reply-to-sender diff --git a/test/notmuch-test b/test/notmuch-test index e40ef86..6a99ae3 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -33,6 +33,7 @@ TESTS=" thread-naming raw reply + reply-to-sender dump-restore uuencode thread-order diff --git a/test/reply-to-sender b/test/reply-to-sender new file mode 100755 index 0000000..c7d15bb --- /dev/null +++ b/test/reply-to-sender @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +test_description="\"notmuch reply --reply-to=sender\" in several variations" +. ./test-lib.sh + +test_begin_subtest "Basic reply-to-sender" +add_message '[from]="Sender "' \ + [to]=test_suite@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="basic reply-to-sender test"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> basic reply-to-sender test" + +test_begin_subtest "From Us, Basic reply to message" +add_message '[from]="Notmuch Test Suite "' \ + '[to]="Recipient "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="basic reply-to-from-us test"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Recipient +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite wrote: +> basic reply-to-from-us test" + +test_begin_subtest "Multiple recipients" +add_message '[from]="Sender "' \ + '[to]="test_suite@notmuchmail.org, Someone Else "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="Multiple recipients"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> Multiple recipients" + +test_begin_subtest "From Us, Multiple TO recipients" +add_message '[from]="Notmuch Test Suite "' \ + '[to]="Recipient , Someone Else "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="From Us, Multiple TO recipients"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Recipient , Someone Else +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite wrote: +> From Us, Multiple TO recipients" + +test_begin_subtest "Reply with CC" +add_message '[from]="Sender "' \ + [to]=test_suite@notmuchmail.org \ + '[cc]="Other Parties "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="reply with CC"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> reply with CC" + +test_begin_subtest "From Us, Reply with CC" +add_message '[from]="Notmuch Test Suite "' \ + '[to]="Recipient "' \ + '[cc]="Other Parties "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="reply with CC"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Recipient +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite wrote: +> reply with CC" + +test_begin_subtest "From Us, Reply no TO but with CC" +add_message '[from]="Notmuch Test Suite "' \ + '[cc]="Other Parties "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="reply with CC"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +Cc: Other Parties +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite wrote: +> reply with CC" + +test_begin_subtest "Reply from alternate address" +add_message '[from]="Sender "' \ + [to]=test_suite_other@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="reply from alternate address"' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> reply from alternate address" + +test_begin_subtest "Support for Reply-To" +add_message '[from]="Sender "' \ + [to]=test_suite@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="support for reply-to"' \ + '[reply-to]="Sender "' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> support for reply-to" + +test_begin_subtest "Support for Reply-To with multiple recipients" +add_message '[from]="Sender "' \ + '[to]="test_suite@notmuchmail.org, Someone Else "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="support for reply-to with multiple recipients"' \ + '[reply-to]="Sender "' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> support for reply-to with multiple recipients" + +test_begin_subtest "Un-munging Reply-To" +add_message '[from]="Sender "' \ + '[to]="Some List "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="Un-munging Reply-To"' \ + '[reply-to]="Evil Munging List "' + +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> Un-munging Reply-To" + +test_begin_subtest "Message with header of exactly 200 bytes" +add_message '[subject]="This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces"' \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="200-byte header"' +output=$(notmuch reply --reply-to=sender id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite wrote: +> 200-byte header" +test_done -- cgit v1.2.3 From fa73ffc614b9966bede3250fdfe6a3755d684f5a Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Fri, 13 Jan 2012 22:08:23 -0500 Subject: Fix build warning: "/*" within comment --- notmuch-show.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notmuch-show.c b/notmuch-show.c index 87a1c90..d14dac9 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -677,7 +677,7 @@ format_part_content_json (GMimeObject *part) if (g_mime_content_type_is_type (content_type, "text", "*")) { - /* For non-HTML text/* parts, we include the content in the + /* For non-HTML text parts, we include the content in the * JSON. Since JSON must be Unicode, we handle charset * decoding here and do not report a charset to the caller. * For text/html parts, we do not include the content. If a -- cgit v1.2.3 From baa2c9721d850ea95857f44ba0b44147c80f7998 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 18:39:28 +0200 Subject: NEWS: add news items for reply to sender --- NEWS | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/NEWS b/NEWS index bf21e64..1161c22 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,26 @@ +Notmuch 0.12 (2012-xx-xx) +========================= + +Command-Line Interface +---------------------- + +Reply to sender + + "notmuch reply" has gained the ability to create a reply template + for replying just to the sender of the message, in addition to reply + to all. The feature is available through the new command line option + --reply-to=(all|sender). + +Emacs Interface +--------------- + +Reply to sender + + The Emacs interface has, with the new CLI support, gained the + ability to reply to sender in addition to reply to all. In both show + and search modes, 'r' has been bound to reply to sender, replacing + reply to all, which now has key binding 'R'. + Notmuch 0.11 (2012-01-13) ========================= -- cgit v1.2.3 From 4b256ff557e924fbaffca144d25a9d5f92026146 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Tue, 27 Dec 2011 16:47:14 +0000 Subject: emacs: Don't attempt to colour tags in `notmuch-show-mode'. The tags were coloured using text properties. Unfortunately that text (the header line) also has an overlay, which overrides the text properties. There's not point in applying text properties that will never be seen. --- emacs/notmuch-show.el | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 03c1f6b..1a250a3 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -221,10 +221,7 @@ indentation." (goto-char (notmuch-show-message-top)) (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) (let ((inhibit-read-only t)) - (replace-match (concat "(" - (propertize (mapconcat 'identity tags " ") - 'face 'notmuch-tag-face) - ")")))))) + (replace-match (concat "(" (mapconcat 'identity tags " ") ")")))))) (defun notmuch-show-clean-address (address) "Try to clean a single email ADDRESS for display. Return @@ -256,8 +253,7 @@ message at DEPTH in the current thread." " (" date ") (" - (propertize (mapconcat 'identity tags " ") - 'face 'notmuch-tag-face) + (mapconcat 'identity tags " ") ")\n") (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face))) -- cgit v1.2.3 From ef5c1d73f8e736425682dc4db0ce285e6e2de0f5 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 28 Dec 2011 08:29:58 +0000 Subject: emacs: Cycle through notmuch buffers rather than jumping to the last. As suggested by j4ni in #notmuch, rename `notmuch-jump-to-recent-buffer' as `notmuch-cycle-notmuch-buffers' and have it behave accordingly. Consider `message-mode' buffers to be of interest. --- emacs/notmuch.el | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index d952c41..ef4dcc7 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -1069,21 +1069,39 @@ current search results AND that are tagged with the given tag." (interactive) (notmuch-hello)) +(defun notmuch-interesting-buffer (b) + "Is the current buffer of interest to a notmuch user?" + (with-current-buffer b + (memq major-mode '(notmuch-show-mode + notmuch-search-mode + notmuch-hello-mode + message-mode)))) + ;;;###autoload -(defun notmuch-jump-to-recent-buffer () - "Jump to the most recent notmuch buffer (search, show or hello). +(defun notmuch-cycle-notmuch-buffers () + "Cycle through any existing notmuch buffers (search, show or hello). -If no recent buffer is found, run `notmuch'." +If the current buffer is the only notmuch buffer, bury it. If no +notmuch buffers exist, run `notmuch'." (interactive) - (let ((last - (loop for buffer in (buffer-list) - if (with-current-buffer buffer - (memq major-mode '(notmuch-show-mode - notmuch-search-mode - notmuch-hello-mode))) - return buffer))) - (if last - (switch-to-buffer last) + + (let (start first) + ;; If the current buffer is a notmuch buffer, remember it and then + ;; bury it. + (when (notmuch-interesting-buffer (current-buffer)) + (setq start (current-buffer)) + (bury-buffer)) + + ;; Find the first notmuch buffer. + (setq first (loop for buffer in (buffer-list) + if (notmuch-interesting-buffer buffer) + return buffer)) + + (if first + ;; If the first one we found is any other than the starting + ;; buffer, switch to it. + (unless (eq first start) + (switch-to-buffer first)) (notmuch)))) (setq mail-user-agent 'notmuch-user-agent) -- cgit v1.2.3 From 42e8f66edf91095bb87b3ee7713b94002ffd3edc Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Sat, 14 Jan 2012 10:09:37 +0100 Subject: test: don't bail out of `run_emacs' too early when missing prereqs When running the Emacs tests in verbose mode, only the first missing prereq is reported because the `run_emacs' function is short-circuited early: #+begin_example emacs: Testing emacs interface missing prerequisites: [0] emacs(1) skipping test: [0] Basic notmuch-hello view in emacs SKIP [0] Basic notmuch-hello view in emacs #+end_example This can lead to situations reminiscent of "dependency hell", so instead of returning based on each individual `test_require_external_prereq's exit status, we now do so only after checking all the prereqs: #+begin_example emacs: Testing emacs interface missing prerequisites: [0] dtach(1) emacs(1) emacsclient(1) skipping test: [0] Basic notmuch-hello view in emacs SKIP [0] Basic notmuch-hello view in emacs #+end_example Also added missing prereq for dtach(1). --- test/test-lib.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 82767c0..d1fbc05 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -907,8 +907,11 @@ EOF test_emacs () { # test dependencies beforehand to avoid the waiting loop below - test_require_external_prereq emacs || return - test_require_external_prereq emacsclient || return + missing_dependencies= + test_require_external_prereq dtach || missing_dependencies=1 + test_require_external_prereq emacs || missing_dependencies=1 + test_require_external_prereq emacsclient || missing_dependencies=1 + test -z "$missing_dependencies" || return if [ -z "$EMACS_SERVER" ]; then server_name="notmuch-test-suite-$$" -- cgit v1.2.3 From 7ddd849015759a329bf8fef8c8b5a93359408962 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 16 Jan 2012 13:39:41 +0100 Subject: py3k: add a specialized version of _str for python3 All strings are unicode strings in python 3 and the basestring and unicode types are removed hence the need for a specialized version. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/globals.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py index 32ed9ae..4138460 100644 --- a/bindings/python/notmuch/globals.py +++ b/bindings/python/notmuch/globals.py @@ -31,12 +31,34 @@ if sys.version_info[0] == 2: class Python3StringMixIn(object): def __str__(self): return unicode(self).encode('utf-8') + + + def _str(value): + """Ensure a nicely utf-8 encoded string to pass to libnotmuch + + C++ code expects strings to be well formatted and + unicode strings to have no null bytes.""" + if not isinstance(value, basestring): + raise TypeError("Expected str or unicode, got %s" % type(value)) + if isinstance(value, unicode): + return value.encode('UTF-8') + return value else: class Python3StringMixIn(object): def __str__(self): return self.__unicode__() + def _str(value): + """Ensure a nicely utf-8 encoded string to pass to libnotmuch + + C++ code expects strings to be well formatted and + unicode strings to have no null bytes.""" + if not isinstance(value, str): + raise TypeError("Expected str, got %s" % type(value)) + return value.encode('UTF-8') + + class Enum(object): """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc...""" def __init__(self, names): @@ -202,18 +224,6 @@ class NotInitializedError(NotmuchError): status = STATUS.NOT_INITIALIZED -def _str(value): - """Ensure a nicely utf-8 encoded string to pass to libnotmuch - - C++ code expects strings to be well formatted and - unicode strings to have no null bytes.""" - if not isinstance(value, basestring): - raise TypeError("Expected str or unicode, got %s" % str(type(value))) - if isinstance(value, unicode): - return value.encode('UTF-8') - return value - - class NotmuchDatabaseS(Structure): pass NotmuchDatabaseP = POINTER(NotmuchDatabaseS) -- cgit v1.2.3 From 93150f6467e10d075bf5cf4f27eb632a4d5d63dd Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 23:49:49 +0200 Subject: test: add known broken test for reply from address in named group list If a message was received to the user's address that was in a named group list, notmuch reply does not use that address for picking the from address. Groups lists are of the form: foo:bar@example.com,baz@example.com; Signed-off-by: Jani Nikula --- test/reply | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/reply b/test/reply index c0b8e26..196535a 100755 --- a/test/reply +++ b/test/reply @@ -72,6 +72,25 @@ References: <${gen_msg_id}> On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: > reply from alternate address" +test_begin_subtest "Reply from address in named group list" +test_subtest_known_broken +add_message '[from]="Sender "' \ + '[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \ + [cc]=test_suite_other@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="Reply from address in named group list"' + +output=$(notmuch reply id:${gen_msg_id}) +test_expect_equal "$output" "From: Notmuch Test Suite +Subject: Re: notmuch-reply-test +To: Sender , someone@example.com +In-Reply-To: <${gen_msg_id}> +References: <${gen_msg_id}> + +On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: +> Reply from address in named group list" + test_begin_subtest "Support for Reply-To" add_message '[from]="Sender "' \ [to]=test_suite@notmuchmail.org \ -- cgit v1.2.3 From 982096d79df8d47ac62d9a74fa0a9baa9c008812 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sat, 14 Jan 2012 23:49:50 +0200 Subject: cli: pick the user's address in a group list as from address Messages received to a group list were not replied to using the from address in the list. Fix it. Signed-off-by: Jani Nikula --- notmuch-reply.c | 2 +- test/reply | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/notmuch-reply.c b/notmuch-reply.c index da3acce..0f682db 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -203,7 +203,7 @@ scan_address_list (InternetAddressList *list, if (group_list == NULL) continue; - n += scan_address_list (group_list, config, message, type, NULL); + n += scan_address_list (group_list, config, message, type, user_from); } else { InternetAddressMailbox *mailbox; const char *name; diff --git a/test/reply b/test/reply index 196535a..e4e16eb 100755 --- a/test/reply +++ b/test/reply @@ -73,7 +73,6 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: > reply from alternate address" test_begin_subtest "Reply from address in named group list" -test_subtest_known_broken add_message '[from]="Sender "' \ '[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \ [cc]=test_suite_other@notmuchmail.org \ -- cgit v1.2.3 From 3b76adf9e2c026dd03b820f4c6eab50e25444113 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 14 Jan 2012 19:17:33 -0500 Subject: lib: Add support for automatically excluding tags from queries This is useful for tags like "deleted" and "spam" that people generally want to exclude from query results. These exclusions will be overridden if a tag is explicitly mentioned in a query. --- lib/notmuch-private.h | 2 +- lib/notmuch.h | 6 ++++++ lib/query.cc | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 60a932f..7bf153e 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -458,7 +458,7 @@ typedef struct _notmuch_string_node { struct _notmuch_string_node *next; } notmuch_string_node_t; -typedef struct _notmuch_string_list { +typedef struct visible _notmuch_string_list { int length; notmuch_string_node_t *head; notmuch_string_node_t **tail; diff --git a/lib/notmuch.h b/lib/notmuch.h index 9f23a10..7929fe7 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -457,6 +457,12 @@ notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort); notmuch_sort_t notmuch_query_get_sort (notmuch_query_t *query); +/* Add a tag that will be excluded from the query results by default. + * This exclusion will be overridden if this tag appears explicitly in + * the query. */ +void +notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag); + /* Execute a query for threads, returning a notmuch_threads_t object * which can be used to iterate over the results. The returned threads * object is owned by the query and as such, will only be valid until diff --git a/lib/query.cc b/lib/query.cc index b6c0f12..0b36602 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -27,6 +27,7 @@ struct _notmuch_query { notmuch_database_t *notmuch; const char *query_string; notmuch_sort_t sort; + notmuch_string_list_t *exclude_terms; }; typedef struct _notmuch_mset_messages { @@ -76,6 +77,8 @@ notmuch_query_create (notmuch_database_t *notmuch, query->sort = NOTMUCH_SORT_NEWEST_FIRST; + query->exclude_terms = _notmuch_string_list_create (query); + return query; } @@ -97,6 +100,13 @@ notmuch_query_get_sort (notmuch_query_t *query) return query->sort; } +void +notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag) +{ + char *term = talloc_asprintf (query, "%s%s", _find_prefix ("tag"), tag); + _notmuch_string_list_append (query->exclude_terms, term); +} + /* We end up having to call the destructors explicitly because we had * to use "placement new" in order to initialize C++ objects within a * block that we allocated with talloc. So C++ is making talloc @@ -112,6 +122,27 @@ _notmuch_messages_destructor (notmuch_mset_messages_t *messages) return 0; } +/* Return a query that does not match messages with the excluded tags + * registered with the query. Any tags that explicitly appear in + * xquery will not be excluded. */ +static Xapian::Query +_notmuch_exclude_tags (notmuch_query_t *query, Xapian::Query xquery) +{ + for (notmuch_string_node_t *term = query->exclude_terms->head; term; + term = term->next) { + Xapian::TermIterator it = xquery.get_terms_begin (); + Xapian::TermIterator end = xquery.get_terms_end (); + for (; it != end; it++) { + if ((*it).compare (term->string) == 0) + break; + } + if (it == end) + xquery = Xapian::Query (Xapian::Query::OP_AND_NOT, + xquery, Xapian::Query (term->string)); + } + return xquery; +} + notmuch_messages_t * notmuch_query_search_messages (notmuch_query_t *query) { @@ -157,6 +188,8 @@ notmuch_query_search_messages (notmuch_query_t *query) mail_query, string_query); } + final_query = _notmuch_exclude_tags (query, final_query); + enquire.set_weighting_scheme (Xapian::BoolWeight()); switch (query->sort) { @@ -436,6 +469,8 @@ notmuch_query_count_messages (notmuch_query_t *query) mail_query, string_query); } + final_query = _notmuch_exclude_tags (query, final_query); + enquire.set_weighting_scheme(Xapian::BoolWeight()); enquire.set_docid_order(Xapian::Enquire::ASCENDING); -- cgit v1.2.3 From 42a907992823030f070fc395a174f779998ca6f5 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 14 Jan 2012 19:17:34 -0500 Subject: search: Support automatic tag exclusions This adds a "search" section to the config file and an "auto_tag_exclusions" setting in that section. The search and count commands pass tag tags from the configuration to the library. --- notmuch-client.h | 8 ++++++++ notmuch-config.c | 42 ++++++++++++++++++++++++++++++++++++++++++ notmuch-count.c | 8 ++++++++ notmuch-search.c | 8 ++++++++ test/search | 18 ++++++++++++++++++ 5 files changed, 84 insertions(+) diff --git a/notmuch-client.h b/notmuch-client.h index 517c010..62ede28 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -235,6 +235,14 @@ void notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config, notmuch_bool_t synchronize_flags); +const char ** +notmuch_config_get_auto_exclude_tags (notmuch_config_t *config, size_t *length); + +void +notmuch_config_set_auto_exclude_tags (notmuch_config_t *config, + const char *list[], + size_t length); + int notmuch_run_hook (const char *db_path, const char *hook); diff --git a/notmuch-config.c b/notmuch-config.c index d697138..3d4d5b9 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -84,6 +84,15 @@ static const char maildir_config_comment[] = "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n" "\tcommands will notice tag changes and update flags in filenames\n"; +static const char search_config_comment[] = + " Search configuration\n" + "\n" + " The following option is supported here:\n" + "\n" + "\tauto_exclude_tags A ;-separated list of tags that will be\n" + "\t excluded from search results by default. Using an excluded tag\n" + "\t in a query will override that exclusion.\n"; + struct _notmuch_config { char *filename; GKeyFile *key_file; @@ -96,6 +105,8 @@ struct _notmuch_config { const char **new_tags; size_t new_tags_length; notmuch_bool_t maildir_synchronize_flags; + const char **auto_exclude_tags; + size_t auto_exclude_tags_length; }; static int @@ -221,6 +232,7 @@ notmuch_config_open (void *ctx, int file_had_new_group; int file_had_user_group; int file_had_maildir_group; + int file_had_search_group; if (is_new_ret) *is_new_ret = 0; @@ -252,6 +264,8 @@ notmuch_config_open (void *ctx, config->new_tags = NULL; config->new_tags_length = 0; config->maildir_synchronize_flags = TRUE; + config->auto_exclude_tags = NULL; + config->auto_exclude_tags_length = 0; if (! g_key_file_load_from_file (config->key_file, config->filename, @@ -295,6 +309,7 @@ notmuch_config_open (void *ctx, file_had_new_group = g_key_file_has_group (config->key_file, "new"); file_had_user_group = g_key_file_has_group (config->key_file, "user"); file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir"); + file_had_search_group = g_key_file_has_group (config->key_file, "search"); if (notmuch_config_get_database_path (config) == NULL) { @@ -345,6 +360,11 @@ notmuch_config_open (void *ctx, notmuch_config_set_new_tags (config, tags, 2); } + if (notmuch_config_get_auto_exclude_tags (config, &tmp) == NULL) { + const char *tags[] = { "deleted", "spam" }; + notmuch_config_set_auto_exclude_tags (config, tags, 2); + } + error = NULL; config->maildir_synchronize_flags = g_key_file_get_boolean (config->key_file, @@ -387,6 +407,11 @@ notmuch_config_open (void *ctx, maildir_config_comment, NULL); } + if (! file_had_search_group) { + g_key_file_set_comment (config->key_file, "search", NULL, + search_config_comment, NULL); + } + if (is_new_ret) *is_new_ret = is_new; @@ -597,6 +622,23 @@ notmuch_config_set_new_tags (notmuch_config_t *config, &(config->new_tags)); } +const char ** +notmuch_config_get_auto_exclude_tags (notmuch_config_t *config, size_t *length) +{ + return _config_get_list (config, "search", "auto_exclude_tags", + &(config->auto_exclude_tags), + &(config->auto_exclude_tags_length), length); +} + +void +notmuch_config_set_auto_exclude_tags (notmuch_config_t *config, + const char *list[], + size_t length) +{ + _config_set_list (config, "search", "auto_exclude_tags", list, length, + &(config->auto_exclude_tags)); +} + /* Given a configuration item of the form . return the * component group and key. If any error occurs, print a message on * stderr and return 1. Otherwise, return 0. diff --git a/notmuch-count.c b/notmuch-count.c index 0982f99..f77861e 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -35,6 +35,9 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) char *query_str; int opt_index; int output = OUTPUT_MESSAGES; + const char **auto_exclude_tags; + size_t auto_exclude_tags_length; + unsigned int i; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &output, "output", 'o', @@ -75,6 +78,11 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) return 1; } + auto_exclude_tags = notmuch_config_get_auto_exclude_tags + (config, &auto_exclude_tags_length); + for (i = 0; i < auto_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, auto_exclude_tags[i]); + switch (output) { case OUTPUT_MESSAGES: printf ("%u\n", notmuch_query_count_messages (query)); diff --git a/notmuch-search.c b/notmuch-search.c index 4baab56..8867aab 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -423,6 +423,9 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) output_t output = OUTPUT_SUMMARY; int offset = 0; int limit = -1; /* unlimited */ + const char **auto_exclude_tags; + size_t auto_exclude_tags_length; + unsigned int i; enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT } format_sel = NOTMUCH_FORMAT_TEXT; @@ -490,6 +493,11 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) notmuch_query_set_sort (query, sort); + auto_exclude_tags = notmuch_config_get_auto_exclude_tags + (config, &auto_exclude_tags_length); + for (i = 0; i < auto_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, auto_exclude_tags[i]); + switch (output) { default: case OUTPUT_SUMMARY: diff --git a/test/search b/test/search index a7a0b18..bf965e7 100755 --- a/test/search +++ b/test/search @@ -129,4 +129,22 @@ add_message '[subject]="utf8-message-body-subject"' '[date]="Sat, 01 Jan 2000 12 output=$(notmuch search "bödý" | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)" +test_begin_subtest "Exclude \"deleted\" messages from search" +generate_message '[subject]="Not deleted"' +generate_message '[subject]="Deleted"' +notmuch new > /dev/null +notmuch tag +deleted id:$gen_msg_id +output=$(notmuch search subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)" + +test_begin_subtest "Exclude \"deleted\" messages from search, overridden" +output=$(notmuch search subject:deleted and tag:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Deleted (deleted inbox unread)" + +test_begin_subtest "Exclude \"deleted\" messages from threads" +add_message '[subject]="Not deleted reply"' '[in-reply-to]="<$gen_msg_id>"' +output=$(notmuch search subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) +thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)" + test_done -- cgit v1.2.3 From d2df1eca1e661f4354517b5030489ea162719df8 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 17 Jan 2012 13:00:15 +0200 Subject: NEWS: consistent 2-space indentation In NEWS file, indentation for item descriptions is generally 2 spaces but in a few cases there were 3 or 4 (4 caused different markdown handling) space indentations. Indentation in those lines are brought to consistent 2-space indentation. --- NEWS | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index 1161c22..1e561a9 100644 --- a/NEWS +++ b/NEWS @@ -133,8 +133,8 @@ Bug-fix release. Fix crash in python bindings. - The python bindings did not call g_type_init, which caused crashes - for some, but not all users. + The python bindings did not call g_type_init, which caused crashes + for some, but not all users. Notmuch 0.10.1 (2011-11-25) =========================== @@ -204,8 +204,8 @@ Add keybinding ('c I') for stashing Message-ID's without an id: prefix Do not query on notmuch-search exit - It is harmless to kill the external notmuch process, so the user - is no longer interrogated when they interrupt a search. + It is harmless to kill the external notmuch process, so the user + is no longer interrogated when they interrupt a search. Performance ----------- @@ -234,9 +234,9 @@ mailing list. nmbug - share tags with a given prefix - nmbug helps maintain a git repo containing all tags with a given - prefix (by default "notmuch::"). Tags can be shared by commiting - them to git in one location and restoring in another. + nmbug helps maintain a git repo containing all tags with a given + prefix (by default "notmuch::"). Tags can be shared by commiting + them to git in one location and restoring in another. Notmuch 0.9 (2011-10-01) ======================== @@ -621,7 +621,7 @@ Ruby bindings are now much more complete s1.union(s2) s2 -= s1 - Removed: + Removed: - len(Messages()) as it exhausted the iterator. Use len(list(Messages())) or Query.count_messages() to get the length. -- cgit v1.2.3 From efa5d6cb32825f4744ddcfdcfa2fc354d9740ce1 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 17 Jan 2012 08:01:48 -0400 Subject: Revert "emacs: Don't attempt to colour tags in `notmuch-show-mode'." This reverts commit 4b256ff557e924fbaffca144d25a9d5f92026146. According to id:"87aa5nlwwg.fsf@praet.org" and followup messages, the assumptions of the patch seem not to hold in emacs 23. --- emacs/notmuch-show.el | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 1a250a3..03c1f6b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -221,7 +221,10 @@ indentation." (goto-char (notmuch-show-message-top)) (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) (let ((inhibit-read-only t)) - (replace-match (concat "(" (mapconcat 'identity tags " ") ")")))))) + (replace-match (concat "(" + (propertize (mapconcat 'identity tags " ") + 'face 'notmuch-tag-face) + ")")))))) (defun notmuch-show-clean-address (address) "Try to clean a single email ADDRESS for display. Return @@ -253,7 +256,8 @@ message at DEPTH in the current thread." " (" date ") (" - (mapconcat 'identity tags " ") + (propertize (mapconcat 'identity tags " ") + 'face 'notmuch-tag-face) ")\n") (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face))) -- cgit v1.2.3 From 8ea82928b91e847298e4586f9db9734e727a418a Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 17 Jan 2012 20:16:03 +0200 Subject: fix .gitignore for gzipped man pages --- .gitignore | 1 - man/.gitignore | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 man/.gitignore diff --git a/.gitignore b/.gitignore index d64ec9f..d428290 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ tags /notmuch notmuch.sym notmuch-shared -notmuch.1.gz libnotmuch.so* libnotmuch*.dylib *.[ao] diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 0000000..26ead20 --- /dev/null +++ b/man/.gitignore @@ -0,0 +1,2 @@ +# ignore gzipped man pages +*.[0-9].gz -- cgit v1.2.3 From d51b7842149dcaedd02c4e5b6ba74a5bccd926a9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 17 Jan 2012 08:47:51 -0400 Subject: Start devel directory for developer tools and documentation. We had a lot of back and forth about the name of this directory, but nothing very conclusive. In the end, I just chose "devel" just to move on. --- RELEASING | 113 ---------------------- TODO | 285 -------------------------------------------------------- devel/RELEASING | 113 ++++++++++++++++++++++ devel/TODO | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 398 insertions(+), 398 deletions(-) delete mode 100644 RELEASING delete mode 100644 TODO create mode 100644 devel/RELEASING create mode 100644 devel/TODO diff --git a/RELEASING b/RELEASING deleted file mode 100644 index 88dab04..0000000 --- a/RELEASING +++ /dev/null @@ -1,113 +0,0 @@ -Here are the steps to follow to create a new notmuch release. - -These steps assume that a process (not described here) has already -been followed to determine the features and bug fixes to be included -in a release, and that adequate testing by the community has already -been performed. The little bit of testing performed here is a safety -check, and not a substitute for wider testing. - -OK, so the code to be released is present and committed to your git -repository. From here, there are just a few steps to release: - -1) Verify that the NEWS file is up to date. - - Read through the entry at the top of the NEWS file and see if - you are aware of any major features recently added that are - not mentioned there. If so, please add them, (and ask the - authors of the commits to update NEWS in the future). - -2) Verify that the library version in lib/Makefile.local is correct - - See the instructions there for how to increment it. - - The version should have been updated with any commits that - added API _in a non-upwardly compatible_ way, but do check - that that is the case. The command below can be useful for - inspecting header-file changes since the last release X.Y: - - git diff X.Y..HEAD -- lib/notmuch.h - - Commit this change, if any. - -3) Update the debian/libnotmuchX.symbols file - - If the library version changed at all (step 2) it probably - means that symbols have changed/been added, in which case the - debian symbols file also needs to be updated: - - dpkg-buildpackage -uc -us - dpkg-gensymbols -plibnotmuchX | patch -p0 - - Carefully review the changes to debian/libnotmuch1.symbols to - make sure there are no unexpected changes. Remove any debian - versions from symbols. - - Commit this change, if any. - -4) Upgrade the version in the file "version" - - The scheme for the release number is as follows: - - A major milestone in usability causes an increase in the major - number, yielding a two-component version with a minor number - of 0, (such as "1.0" or "2.0"). - - Otherwise, releases with changes in features cause an increase - in the minor number, yielding a two-component version, (such - as "1.1" or "1.2"). - - Finally, releases that do not change "features" but are merely - bug fixes either increase the micro number or add it (starting - at ".1" if not present). So a bug-fix release from "1.0" would - be "1.0.1" and a subsequent bug-fix release would be "1.0.2" - etc. - - When you are happy with the file 'version', run - - make update-versions - - to propagate the version to the other places needed. - - Commit these changes. - -5) Create an entry for the new release in debian/changelog - - The syntax of this file is tightly restricted, but the - available emacs mode (see the dpkg-dev-el package) helps. - The entries here will be the Debian-relevant single-line - description of changes from the NEWS entry. And the version - must match the version in the next step. - - Commit this change. - - XXX: It would be great if this step were automated as part of - release, (taking entries from NEWS and the version from the - version file, and creating a new commit, etc.) - -6) Run "make release" which will perform the following steps. - - Note: in order to really upload anything, set the make variable - REALLY_UPLOAD=yes - - * Ensure that the version consists only of digits and periods - * Ensure that version and debian/changelog have the same version - * Verify that the source tree is clean - * Compile the current notmuch code (aborting release if it fails) - * Run the notmuch test suite (aborting release if it fails) - * Check that no release exists with the current version - * Make a signed tag - * Generate a tar file from this tag - * Generate a .sha1 sum file for the tar file and GPG sign it. - * Commit a (delta for a) copy of the tar file using pristine-tar - * Tag for the debian version - * if REALLY_UPLOAD=yes - - push the signed tag to the origin - XXX FIXME push debian tag - - scp tarball to web site - * Provide some text for the release announcement (see below). - -7) Send a message to notmuch@notmuchmail.org to announce the release. - - Use the text provided from "make release" above, (if for some - reason you lose this message, "make release-message" prints - it again for you. diff --git a/TODO b/TODO deleted file mode 100644 index 4dda6f4..0000000 --- a/TODO +++ /dev/null @@ -1,285 +0,0 @@ -Fix the things that are causing the most pain to new users ----------------------------------------------------------- -1. A new import is tagging all messages as "inbox" -- total pain - -Emacs interface (notmuch.el) ----------------------------- -Add notmuch-bcc and notmuch-cc for setting default Bcc and Cc values, -(should affect the message-setup-hook). - -Switch the notmuch-search view to use "notmuch search --format=json" -to fix large classes of bugs regarding poorly-escaped output and lame -regular expressions. (The most recently found, unfixed example is the -sender's name containing ';' which causes emacs to drop a search -result.) This may require removing the outer array from the current -"notmuch search --format=json" results. - -Fix '*' to work by simply calling '+' or '-' on a region consisting of -the entire buffer, (this would avoid one race condition---while still -leaving other race conditions---but could also potentially make '*' a -very expensive operation). - -Add a global keybinding table for notmuch, and then view-specific -tables that add to it. - -Add a '|' binding from the search view. - -Add support for choosing from one of the user's configured email -addresses for the From line. - -Make 'notmuch-show-pipe-message have a private history. - -Add support for a delete keybinding that adds a "deleted" tag to the -current message/thread and make searches not return deleted messages -by default, (unless the user asks explicitly for deleted messages in -the search query). - -Add keybindings for next/previous thread. - -Add support to "mute" a thread (add a "muted" tag and then don't -display threads in searches by default where any message of the thread -has the "muted" tag). - -Make '=' count from the end rather than from the beginning if more -than half-way through the buffer. - -Fix to automatically wrap long headers (for RFC compliance) before -sending. This should probably just be fixed in message-mode itself, -(but perhaps we can have a notmuch-message-mode that layers this on -top). - -Stop hiding the headers so much in the thread-view mode. - -Allow opening a message in thread-view mode by clicking on either -line. - -Automatically open a message when navigating to it with N or P. - -Change 'a' command in thread-view mode to only archive open messages. - -Add a binding to open all closed messages. - -Change the 'a'rchive command in the thread view to only archive open -messages. - -Completion ----------- -Fix bash completion to complete multiple search options (both --first -and *then* --max-threads), and also complete value for --sort= -(oldest-first or newest-first). - -notmuch command-line tool -------------------------- -Add support to "notmuch search" and "notmuch show" to allow for -listing of duplicate messages, (distinct filenames with the same -Message-ID). I'm not sure what the option should be named. Perhaps ---with-duplicates ? - -Add a -0 option to "notmuch search" so that one can safely deal with -any filename with: - - notmuch search --output=files -0 | xargs -0 - -"notmuch setup" should use realpath() before replacing the -configuration file. The ensures that the final target file of any -intermediate symbolic links is what is actually replaced, (rather than -any symbolic link). - -Replace "notmuch reply" with "notmuch compose --reply ". -This would enable a plain "notmuch compose" to be used to construct an -initial message, (which would then have the properly configured name -and email address in the From: line. We could also then easily support -"notmuch compose --from " to support getting at alternate -email addresses. - -Fix the --format=json option to not imply --entire-thread. - -Implement "notmuch search --exclude-threads=" to allow -for excluding muted threads, (and any other negative, thread-based -filtering that the user wants to do). - -Fix "notmuch show" so that the UI doesn't fail to show a thread that -is visible in a search buffer, but happens to no longer match the -current search. (Perhaps add a --matching= -option (or similar) to "notmuch show".) For now, this is being worked -around in the emacs interface by noticing that "notmuch show" returns -nothing and re-rerunning the command without the extra arguments. - -Add a "--format" option to "notmuch search", (something printf-like -for selecting what gets printed). - -Give "notmuch restore" some progress indicator. - -Fix "notmuch restore" to operate in a single pass much like "notmuch -dump" does, rather than doing N searches into the database, each -matching 1/N messages. - -Add a "-f " option to select an alternate configuration -file. - -Allow configuration for filename patterns that should be ignored when -indexing. - -Replace the "notmuch part --part=id" command with "notmuch show ---part=id", (David Edmondson wants to rewrite some of "notmuch show" to -provide more MIME-structure information in its output first). - -Replace the "notmuch search-tags" command with "notmuch search ---output=tags". - -Fix to avoid this ugly message: - - (process:17197): gmime-CRITICAL **: g_mime_message_get_mime_part: assertion `GMIME_IS_MESSAGE (message)' failed - Warning: Not indexing empty mime part. - - This probably means adding a test case to generate that message, - filing an upstream bug against GMime, and then silencing the - notmuch-generated portion of the warning (so that once GMime is - fixed, this is all silent). - -Simplify notmuch-reply to simply print the headers (we have the -original values) rather than calling GMime (which encodes) and adding -the confusing gmime-filter-headers.c code (which decodes). - -notmuch library ---------------- -Add support for custom flag<->tag mappings. In the notmuch -configuration file this could be - - [maildir] - synchronize_flags = R:replied; D*:deleted; S:~unread; - -In the library interface this could be implemented with an array of -structures to define the mapping (flag character, tag name, -inverse-sense bit (~ above), and tag-when-any-file-flagged -vs. tag-when-all-files-flagged (* above)). - -Add an interface to accept a "key" and a byte stream, rather than a -filename. - -Provide a sane syntax for date ranges. First, we don't want to require -both endpoints to be specified. For example it would be nice to be -able to say things like "since:2009-01-1" or "until:2009-01-1" and -have the other endpoint be implicit. Second we'd like to support -relative specifications of time such as "since:'2 months ago'". To do -any of this we're probably going to need to break down an write our -own parser for the query string rather than using Xapian's QueryParser -class. - -Make failure to read a file (such as a permissions problem) a warning -rather than an error (should be similar to the existing warning for a -non-mail file). - -Fix to use the *last* Message-ID header if multiple such headers are -encountered, (I noticed this is one thing that kept me from seeing the -same message-ID values as sup). - -Add support for configuring "virtual tags" which are a tuple of -(tag-name, search-specification). The database is responsible for -ensuring that the virtual tag is always consistent. - -Indicate to the user if two files with the same message ID have -content that is actually different in some interesting way. Perhaps -notmuch initially sees all changes as interesting, and quickly learns -from the user which changes are not interesting (such as the very -common mailing-list footer). - -Fix notmuch_query_count_messages to share code with -notmuch_query_search_messages rather than duplicating code. (And -consider renaming it as well.) - -Provide a mechanism for doing automatic address completion based on -notmuch searches. Here was one proposal made in IRC: - - I guess all it would really have to be would be a way - to configure a series of searches to try in turn, - (presenting ambiguities at a given single level, and - advancing to the next level only if one level - returned no matches). - So then I might have a series that looks like this: - notmuch search --output=address_from tag:address_book_alias - notmuch search --output=address_to tag:sent - notmuch search --output=address_from - I think I might like that quite a bit. - And then we have a story for an address book for - non-emacs users. - -Provide a ~me Xapian synonym for all of the user's configured email -addresses. - -Add symbol hiding so that we don't risk leaking any private symbols -into the shared-library interface. - -Audit all libnotmuch entry points to ensure that all Xapian calls are -wrapped in a try/catch block. - -Fix the "count" functionality to be exact as Olly explained in IRC: - - ojwb> cworth: if you set the check_at_least parameter to the - database size, get_matches_estimated() will be exact - -Fix the threading of a message that has a References: header but no -In-Reply-To: header (see id:"87lixxnxpb.fsf@yoom.home.cworth.org"). - -Search syntax -------------- -Implement support for "tag:*" to expand to all tags. - -Fix "notmuch search to:" to be less confusing. Many users expect this -to search for all messages with a To: header, but it instead searches -for all messages with the word "to". If we don't provide the first -behavior, perhaps we should exit on an error when a configured prefix -is provided with no value? - -Support "*" in all cases and not just as a special case. That is, "* " -should also work, as well as "* and tag:inbox". - -Implement a syntax for requesting set-theoertic operations on results -of multiple searches. For example, I would like to do: - - "tag:inbox" SET-SUBTRACT "tag:muted" - - as well as: - - "tag:notmuch and " SET-INTERSECT - "tag:notmuch and not (tag:merged or tag:postponed)" - - See id:3wdpr282yz2.fsf@testarossa.amd.com for more details on the - use cases of the above. - -Database changes ----------------- -Store a reference term for every message-id that appears in -References. We just started doing this for newly-added documents, but -at the next convenient database-schema upgrade, we should go back and -fix old messages to be consistent. - -Start indexing the List-Id header, (and re-index this header for -existing messages at the next database upgrade). - -Add support for the user to specify custom headers to be indexed (and -re-index these for existing messages at the next database upgrade). - -Save filenames for files detected as "not an email file" in the -database. This would allow for two things: 1. Optimizing "notmuch new" -to not have to look at these files again (since they are potentially -large so the detection could be potentially slow). 2. A "notmuch -search" syntax could be added to allow the user to find these files, -(and perhaps delete them or move them away as appropriate). - -Fix filesystem/notmuch-new race condition by not updating database -mtime for a directory if it is the same as the current mtime. - -Test suite ----------- -Achieve 100% test coverage with the test suite. - -General -------- -Audit everything for dealing with out-of-memory (and drop xutil.c). - -Investigate why the notmuch database is slightly larger than the sup -database for the same corpus of email. - -Makefile should print message teaching user about LD_LIBRARY_PATH (or -similar) if libdir is not set to a directory examined by ldconfig. diff --git a/devel/RELEASING b/devel/RELEASING new file mode 100644 index 0000000..88dab04 --- /dev/null +++ b/devel/RELEASING @@ -0,0 +1,113 @@ +Here are the steps to follow to create a new notmuch release. + +These steps assume that a process (not described here) has already +been followed to determine the features and bug fixes to be included +in a release, and that adequate testing by the community has already +been performed. The little bit of testing performed here is a safety +check, and not a substitute for wider testing. + +OK, so the code to be released is present and committed to your git +repository. From here, there are just a few steps to release: + +1) Verify that the NEWS file is up to date. + + Read through the entry at the top of the NEWS file and see if + you are aware of any major features recently added that are + not mentioned there. If so, please add them, (and ask the + authors of the commits to update NEWS in the future). + +2) Verify that the library version in lib/Makefile.local is correct + + See the instructions there for how to increment it. + + The version should have been updated with any commits that + added API _in a non-upwardly compatible_ way, but do check + that that is the case. The command below can be useful for + inspecting header-file changes since the last release X.Y: + + git diff X.Y..HEAD -- lib/notmuch.h + + Commit this change, if any. + +3) Update the debian/libnotmuchX.symbols file + + If the library version changed at all (step 2) it probably + means that symbols have changed/been added, in which case the + debian symbols file also needs to be updated: + + dpkg-buildpackage -uc -us + dpkg-gensymbols -plibnotmuchX | patch -p0 + + Carefully review the changes to debian/libnotmuch1.symbols to + make sure there are no unexpected changes. Remove any debian + versions from symbols. + + Commit this change, if any. + +4) Upgrade the version in the file "version" + + The scheme for the release number is as follows: + + A major milestone in usability causes an increase in the major + number, yielding a two-component version with a minor number + of 0, (such as "1.0" or "2.0"). + + Otherwise, releases with changes in features cause an increase + in the minor number, yielding a two-component version, (such + as "1.1" or "1.2"). + + Finally, releases that do not change "features" but are merely + bug fixes either increase the micro number or add it (starting + at ".1" if not present). So a bug-fix release from "1.0" would + be "1.0.1" and a subsequent bug-fix release would be "1.0.2" + etc. + + When you are happy with the file 'version', run + + make update-versions + + to propagate the version to the other places needed. + + Commit these changes. + +5) Create an entry for the new release in debian/changelog + + The syntax of this file is tightly restricted, but the + available emacs mode (see the dpkg-dev-el package) helps. + The entries here will be the Debian-relevant single-line + description of changes from the NEWS entry. And the version + must match the version in the next step. + + Commit this change. + + XXX: It would be great if this step were automated as part of + release, (taking entries from NEWS and the version from the + version file, and creating a new commit, etc.) + +6) Run "make release" which will perform the following steps. + + Note: in order to really upload anything, set the make variable + REALLY_UPLOAD=yes + + * Ensure that the version consists only of digits and periods + * Ensure that version and debian/changelog have the same version + * Verify that the source tree is clean + * Compile the current notmuch code (aborting release if it fails) + * Run the notmuch test suite (aborting release if it fails) + * Check that no release exists with the current version + * Make a signed tag + * Generate a tar file from this tag + * Generate a .sha1 sum file for the tar file and GPG sign it. + * Commit a (delta for a) copy of the tar file using pristine-tar + * Tag for the debian version + * if REALLY_UPLOAD=yes + - push the signed tag to the origin + XXX FIXME push debian tag + - scp tarball to web site + * Provide some text for the release announcement (see below). + +7) Send a message to notmuch@notmuchmail.org to announce the release. + + Use the text provided from "make release" above, (if for some + reason you lose this message, "make release-message" prints + it again for you. diff --git a/devel/TODO b/devel/TODO new file mode 100644 index 0000000..4dda6f4 --- /dev/null +++ b/devel/TODO @@ -0,0 +1,285 @@ +Fix the things that are causing the most pain to new users +---------------------------------------------------------- +1. A new import is tagging all messages as "inbox" -- total pain + +Emacs interface (notmuch.el) +---------------------------- +Add notmuch-bcc and notmuch-cc for setting default Bcc and Cc values, +(should affect the message-setup-hook). + +Switch the notmuch-search view to use "notmuch search --format=json" +to fix large classes of bugs regarding poorly-escaped output and lame +regular expressions. (The most recently found, unfixed example is the +sender's name containing ';' which causes emacs to drop a search +result.) This may require removing the outer array from the current +"notmuch search --format=json" results. + +Fix '*' to work by simply calling '+' or '-' on a region consisting of +the entire buffer, (this would avoid one race condition---while still +leaving other race conditions---but could also potentially make '*' a +very expensive operation). + +Add a global keybinding table for notmuch, and then view-specific +tables that add to it. + +Add a '|' binding from the search view. + +Add support for choosing from one of the user's configured email +addresses for the From line. + +Make 'notmuch-show-pipe-message have a private history. + +Add support for a delete keybinding that adds a "deleted" tag to the +current message/thread and make searches not return deleted messages +by default, (unless the user asks explicitly for deleted messages in +the search query). + +Add keybindings for next/previous thread. + +Add support to "mute" a thread (add a "muted" tag and then don't +display threads in searches by default where any message of the thread +has the "muted" tag). + +Make '=' count from the end rather than from the beginning if more +than half-way through the buffer. + +Fix to automatically wrap long headers (for RFC compliance) before +sending. This should probably just be fixed in message-mode itself, +(but perhaps we can have a notmuch-message-mode that layers this on +top). + +Stop hiding the headers so much in the thread-view mode. + +Allow opening a message in thread-view mode by clicking on either +line. + +Automatically open a message when navigating to it with N or P. + +Change 'a' command in thread-view mode to only archive open messages. + +Add a binding to open all closed messages. + +Change the 'a'rchive command in the thread view to only archive open +messages. + +Completion +---------- +Fix bash completion to complete multiple search options (both --first +and *then* --max-threads), and also complete value for --sort= +(oldest-first or newest-first). + +notmuch command-line tool +------------------------- +Add support to "notmuch search" and "notmuch show" to allow for +listing of duplicate messages, (distinct filenames with the same +Message-ID). I'm not sure what the option should be named. Perhaps +--with-duplicates ? + +Add a -0 option to "notmuch search" so that one can safely deal with +any filename with: + + notmuch search --output=files -0 | xargs -0 + +"notmuch setup" should use realpath() before replacing the +configuration file. The ensures that the final target file of any +intermediate symbolic links is what is actually replaced, (rather than +any symbolic link). + +Replace "notmuch reply" with "notmuch compose --reply ". +This would enable a plain "notmuch compose" to be used to construct an +initial message, (which would then have the properly configured name +and email address in the From: line. We could also then easily support +"notmuch compose --from " to support getting at alternate +email addresses. + +Fix the --format=json option to not imply --entire-thread. + +Implement "notmuch search --exclude-threads=" to allow +for excluding muted threads, (and any other negative, thread-based +filtering that the user wants to do). + +Fix "notmuch show" so that the UI doesn't fail to show a thread that +is visible in a search buffer, but happens to no longer match the +current search. (Perhaps add a --matching= +option (or similar) to "notmuch show".) For now, this is being worked +around in the emacs interface by noticing that "notmuch show" returns +nothing and re-rerunning the command without the extra arguments. + +Add a "--format" option to "notmuch search", (something printf-like +for selecting what gets printed). + +Give "notmuch restore" some progress indicator. + +Fix "notmuch restore" to operate in a single pass much like "notmuch +dump" does, rather than doing N searches into the database, each +matching 1/N messages. + +Add a "-f " option to select an alternate configuration +file. + +Allow configuration for filename patterns that should be ignored when +indexing. + +Replace the "notmuch part --part=id" command with "notmuch show +--part=id", (David Edmondson wants to rewrite some of "notmuch show" to +provide more MIME-structure information in its output first). + +Replace the "notmuch search-tags" command with "notmuch search +--output=tags". + +Fix to avoid this ugly message: + + (process:17197): gmime-CRITICAL **: g_mime_message_get_mime_part: assertion `GMIME_IS_MESSAGE (message)' failed + Warning: Not indexing empty mime part. + + This probably means adding a test case to generate that message, + filing an upstream bug against GMime, and then silencing the + notmuch-generated portion of the warning (so that once GMime is + fixed, this is all silent). + +Simplify notmuch-reply to simply print the headers (we have the +original values) rather than calling GMime (which encodes) and adding +the confusing gmime-filter-headers.c code (which decodes). + +notmuch library +--------------- +Add support for custom flag<->tag mappings. In the notmuch +configuration file this could be + + [maildir] + synchronize_flags = R:replied; D*:deleted; S:~unread; + +In the library interface this could be implemented with an array of +structures to define the mapping (flag character, tag name, +inverse-sense bit (~ above), and tag-when-any-file-flagged +vs. tag-when-all-files-flagged (* above)). + +Add an interface to accept a "key" and a byte stream, rather than a +filename. + +Provide a sane syntax for date ranges. First, we don't want to require +both endpoints to be specified. For example it would be nice to be +able to say things like "since:2009-01-1" or "until:2009-01-1" and +have the other endpoint be implicit. Second we'd like to support +relative specifications of time such as "since:'2 months ago'". To do +any of this we're probably going to need to break down an write our +own parser for the query string rather than using Xapian's QueryParser +class. + +Make failure to read a file (such as a permissions problem) a warning +rather than an error (should be similar to the existing warning for a +non-mail file). + +Fix to use the *last* Message-ID header if multiple such headers are +encountered, (I noticed this is one thing that kept me from seeing the +same message-ID values as sup). + +Add support for configuring "virtual tags" which are a tuple of +(tag-name, search-specification). The database is responsible for +ensuring that the virtual tag is always consistent. + +Indicate to the user if two files with the same message ID have +content that is actually different in some interesting way. Perhaps +notmuch initially sees all changes as interesting, and quickly learns +from the user which changes are not interesting (such as the very +common mailing-list footer). + +Fix notmuch_query_count_messages to share code with +notmuch_query_search_messages rather than duplicating code. (And +consider renaming it as well.) + +Provide a mechanism for doing automatic address completion based on +notmuch searches. Here was one proposal made in IRC: + + I guess all it would really have to be would be a way + to configure a series of searches to try in turn, + (presenting ambiguities at a given single level, and + advancing to the next level only if one level + returned no matches). + So then I might have a series that looks like this: + notmuch search --output=address_from tag:address_book_alias + notmuch search --output=address_to tag:sent + notmuch search --output=address_from + I think I might like that quite a bit. + And then we have a story for an address book for + non-emacs users. + +Provide a ~me Xapian synonym for all of the user's configured email +addresses. + +Add symbol hiding so that we don't risk leaking any private symbols +into the shared-library interface. + +Audit all libnotmuch entry points to ensure that all Xapian calls are +wrapped in a try/catch block. + +Fix the "count" functionality to be exact as Olly explained in IRC: + + ojwb> cworth: if you set the check_at_least parameter to the + database size, get_matches_estimated() will be exact + +Fix the threading of a message that has a References: header but no +In-Reply-To: header (see id:"87lixxnxpb.fsf@yoom.home.cworth.org"). + +Search syntax +------------- +Implement support for "tag:*" to expand to all tags. + +Fix "notmuch search to:" to be less confusing. Many users expect this +to search for all messages with a To: header, but it instead searches +for all messages with the word "to". If we don't provide the first +behavior, perhaps we should exit on an error when a configured prefix +is provided with no value? + +Support "*" in all cases and not just as a special case. That is, "* " +should also work, as well as "* and tag:inbox". + +Implement a syntax for requesting set-theoertic operations on results +of multiple searches. For example, I would like to do: + + "tag:inbox" SET-SUBTRACT "tag:muted" + + as well as: + + "tag:notmuch and " SET-INTERSECT + "tag:notmuch and not (tag:merged or tag:postponed)" + + See id:3wdpr282yz2.fsf@testarossa.amd.com for more details on the + use cases of the above. + +Database changes +---------------- +Store a reference term for every message-id that appears in +References. We just started doing this for newly-added documents, but +at the next convenient database-schema upgrade, we should go back and +fix old messages to be consistent. + +Start indexing the List-Id header, (and re-index this header for +existing messages at the next database upgrade). + +Add support for the user to specify custom headers to be indexed (and +re-index these for existing messages at the next database upgrade). + +Save filenames for files detected as "not an email file" in the +database. This would allow for two things: 1. Optimizing "notmuch new" +to not have to look at these files again (since they are potentially +large so the detection could be potentially slow). 2. A "notmuch +search" syntax could be added to allow the user to find these files, +(and perhaps delete them or move them away as appropriate). + +Fix filesystem/notmuch-new race condition by not updating database +mtime for a directory if it is the same as the current mtime. + +Test suite +---------- +Achieve 100% test coverage with the test suite. + +General +------- +Audit everything for dealing with out-of-memory (and drop xutil.c). + +Investigate why the notmuch database is slightly larger than the sup +database for the same corpus of email. + +Makefile should print message teaching user about LD_LIBRARY_PATH (or +similar) if libdir is not set to a directory examined by ldconfig. -- cgit v1.2.3 From edd25db019ada82b2c7499fce337dbed033205cf Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 15 Jan 2012 15:20:23 -0500 Subject: Fix dependency generation for CLI sources Previously, the dependency file list was generated before the CLI sources were added to SRCS, so dependency files weren't generated for CLI sources. This moves that code to after the CLI sources are added. --- Makefile.local | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.local b/Makefile.local index d3bf947..1131dea 100644 --- a/Makefile.local +++ b/Makefile.local @@ -273,10 +273,6 @@ quiet ?= $($(shell echo $1 | sed -e s'/ .*//')) sed 's,'$$(basename $*)'\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ -DEPS := $(SRCS:%.c=.deps/%.d) -DEPS := $(DEPS:%.cc=.deps/%.d) --include $(DEPS) - .PHONY : clean clean: rm -f $(CLEAN); rm -rf .deps @@ -350,3 +346,7 @@ install-desktop: SRCS := $(SRCS) $(notmuch_client_srcs) CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules) notmuch.elc + +DEPS := $(SRCS:%.c=.deps/%.d) +DEPS := $(DEPS:%.cc=.deps/%.d) +-include $(DEPS) -- cgit v1.2.3 From 5c12ee4b5d942b5c9efc79922d25b1cf9934aab0 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 15 Jan 2012 15:20:42 -0500 Subject: Fix dependency generation for compat, test, and util This adds source files in compat, test, and util to SRCS so that the top-level Makefile.local will generate dependency files for them. --- compat/Makefile.local | 2 ++ test/Makefile.local | 1 + util/Makefile.local | 1 + 3 files changed, 4 insertions(+) diff --git a/compat/Makefile.local b/compat/Makefile.local index 504eb71..13f16cd 100644 --- a/compat/Makefile.local +++ b/compat/Makefile.local @@ -12,3 +12,5 @@ endif ifneq ($(HAVE_STRCASESTR),1) notmuch_compat_srcs += $(dir)/strcasestr.c endif + +SRCS := $(SRCS) $(notmuch_compat_srcs) diff --git a/test/Makefile.local b/test/Makefile.local index fa2df73..4a6a4b1 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -28,6 +28,7 @@ test: all test-binaries check: test +SRCS := $(SRCS) $(smtp_dummy_srcs) CLEAN := $(CLEAN) $(dir)/smtp-dummy $(dir)/smtp-dummy.o \ $(dir)/symbol-test $(dir)/symbol-test.o \ $(dir)/arg-test $(dir)/arg-test.o diff --git a/util/Makefile.local b/util/Makefile.local index 26e4c3f..c7cae61 100644 --- a/util/Makefile.local +++ b/util/Makefile.local @@ -10,4 +10,5 @@ libutil_modules := $(libutil_c_srcs:.c=.o) $(dir)/libutil.a: $(libutil_modules) $(call quiet,AR) rcs $@ $^ +SRCS := $(SRCS) $(libutil_c_srcs) CLEAN := $(CLEAN) $(libutil_modules) $(dir)/libutil.a -- cgit v1.2.3 From 643ce61c1babf6e73ca7e03fb907282e7ee3b176 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 16 Jan 2012 11:38:33 +0100 Subject: emacs: logically group def{custom,face}s To allow for expansion whilst keeping everything tidy and organized, move all defcustom/defface variables to the following subgroups, defined in notmuch-lib.el: - Hello - Search - Show - Send - Crypto - Hooks - External Commands - Appearance As an added benefit, defcustom keyword args are now consistently ordered as they appear @ defcustom's docstring (OCD much?). Proper defgroup docstrings and various other improvements by courtesy of Austin Clements. --- emacs/notmuch-address.el | 3 ++- emacs/notmuch-crypto.el | 22 ++++++++++++++-------- emacs/notmuch-hello.el | 33 ++++++++++++++++++--------------- emacs/notmuch-lib.el | 37 +++++++++++++++++++++++++++++++++++-- emacs/notmuch-maildir-fcc.el | 6 +++--- emacs/notmuch-message.el | 2 +- emacs/notmuch-mua.el | 21 +++++++++++---------- emacs/notmuch-show.el | 34 ++++++++++++++++++---------------- emacs/notmuch.el | 35 ++++++++++++++++++++++------------- 9 files changed, 124 insertions(+), 69 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 8eba7a0..2e8b840 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -28,7 +28,8 @@ single argument and output a list of possible matches, one per line." :type 'string - :group 'notmuch) + :group 'notmuch-send + :group 'notmuch-external) (defvar notmuch-address-message-alist-member '("^\\(Resent-\\)?\\(To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\):" diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index ac30098..80ac350 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -34,38 +34,44 @@ The effect of setting this variable can be seen temporarily by providing a prefix when viewing a signed or encrypted message, or by providing a prefix when reloading the message in notmuch-show mode." - :group 'notmuch - :type 'boolean) + :type 'boolean + :group 'notmuch-crypto) (defface notmuch-crypto-part-header '((t (:foreground "blue"))) "Face used for crypto parts headers." - :group 'notmuch) + :group 'notmuch-crypto + :group 'notmuch-faces) (defface notmuch-crypto-signature-good '((t (:background "green" :foreground "black"))) "Face used for good signatures." - :group 'notmuch) + :group 'notmuch-crypto + :group 'notmuch-faces) (defface notmuch-crypto-signature-good-key '((t (:background "orange" :foreground "black"))) "Face used for good signatures." - :group 'notmuch) + :group 'notmuch-crypto + :group 'notmuch-faces) (defface notmuch-crypto-signature-bad '((t (:background "red" :foreground "black"))) "Face used for bad signatures." - :group 'notmuch) + :group 'notmuch-crypto + :group 'notmuch-faces) (defface notmuch-crypto-signature-unknown '((t (:background "red" :foreground "black"))) "Face used for signatures of unknown status." - :group 'notmuch) + :group 'notmuch-crypto + :group 'notmuch-faces) (defface notmuch-crypto-decryption '((t (:background "purple" :foreground "black"))) "Face used for encryption/decryption status messages." - :group 'notmuch) + :group 'notmuch-crypto + :group 'notmuch-faces) (define-button-type 'notmuch-crypto-status-button-type 'action (lambda (button) (message (button-get button 'help-echo))) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 02017ce..bff95ac 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -35,12 +35,12 @@ (defcustom notmuch-recent-searches-max 10 "The number of recent searches to store and display." :type 'integer - :group 'notmuch) + :group 'notmuch-hello) (defcustom notmuch-show-empty-saved-searches nil "Should saved searches with no messages be listed?" :type 'boolean - :group 'notmuch) + :group 'notmuch-hello) (defun notmuch-sort-saved-searches (alist) "Generate an alphabetically sorted saved searches alist." @@ -60,7 +60,7 @@ alist to be used." (const :tag "Sort alphabetically" notmuch-sort-saved-searches) (function :tag "Custom sort function" :value notmuch-sort-saved-searches)) - :group 'notmuch) + :group 'notmuch-hello) (defvar notmuch-hello-indent 4 "How much to indent non-headers.") @@ -68,12 +68,12 @@ alist to be used." (defcustom notmuch-show-logo t "Should the notmuch logo be shown?" :type 'boolean - :group 'notmuch) + :group 'notmuch-hello) (defcustom notmuch-show-all-tags-list nil "Should all tags be shown in the notmuch-hello view?" :type 'boolean - :group 'notmuch) + :group 'notmuch-hello) (defcustom notmuch-hello-tag-list-make-query nil "Function or string to generate queries for the all tags list. @@ -89,12 +89,12 @@ should return a filter for that tag, or nil to hide the tag." (string :tag "Custom filter" :value "tag:unread") (function :tag "Custom filter function")) - :group 'notmuch) + :group 'notmuch-hello) (defcustom notmuch-hello-hide-tags nil "List of tags to be hidden in the \"all tags\"-section." :type '(repeat string) - :group 'notmuch) + :group 'notmuch-hello) (defface notmuch-hello-logo-background '((((class color) @@ -104,7 +104,8 @@ should return a filter for that tag, or nil to hide the tag." (background light)) (:background "white"))) "Background colour for the notmuch logo." - :group 'notmuch) + :group 'notmuch-hello + :group 'notmuch-faces) (defcustom notmuch-column-control t "Controls the number of columns for saved searches/tags in notmuch view. @@ -126,11 +127,11 @@ So: 30. - if you don't want to worry about all of this nonsense, leave this set to `t'." - :group 'notmuch :type '(choice (const :tag "Automatically calculated" t) (integer :tag "Number of characters") - (float :tag "Fraction of window"))) + (float :tag "Fraction of window")) + :group 'notmuch-hello) (defcustom notmuch-hello-thousands-separator " " "The string used as a thousands separator. @@ -138,18 +139,20 @@ So: Typically \",\" in the US and UK and \".\" or \" \" in Europe. The latter is recommended in the SI/ISO 31-0 standard and by the International Bureau of Weights and Measures." - :group 'notmuch - :type 'string) + :type 'string + :group 'notmuch-hello) (defcustom notmuch-hello-mode-hook nil "Functions called after entering `notmuch-hello-mode'." - :group 'notmuch - :type 'hook) + :type 'hook + :group 'notmuch-hello + :group 'notmuch-hooks) (defcustom notmuch-hello-refresh-hook nil "Functions called after updating a `notmuch-hello' buffer." :type 'hook - :group 'notmuch) + :group 'notmuch-hello + :group 'notmuch-hooks) (defvar notmuch-hello-url "http://notmuchmail.org" "The `notmuch' web site.") diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 0f856bf..9242537 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -28,17 +28,50 @@ "Notmuch mail reader for Emacs." :group 'mail) +(defgroup notmuch-hello nil + "Overview of saved searches, tags, etc." + :group 'notmuch) + +(defgroup notmuch-search nil + "Searching and sorting mail." + :group 'notmuch) + +(defgroup notmuch-show nil + "Showing messages and threads." + :group 'notmuch) + +(defgroup notmuch-send nil + "Sending messages from Notmuch." + :group 'notmuch + :group 'message) + +(defgroup notmuch-crypto nil + "Processing and display of cryptographic MIME parts." + :group 'notmuch) + +(defgroup notmuch-hooks nil + "Running custom code on well-defined occasions." + :group 'notmuch) + +(defgroup notmuch-external nil + "Running external commands from within Notmuch." + :group 'notmuch) + +(defgroup notmuch-faces nil + "Graphical attributes for displaying text" + :group 'notmuch) + (defcustom notmuch-search-oldest-first t "Show the oldest mail first when searching." :type 'boolean - :group 'notmuch) + :group 'notmuch-search) ;; (defcustom notmuch-saved-searches nil "A list of saved searches to display." :type '(alist :key-type string :value-type string) - :group 'notmuch) + :group 'notmuch-hello) (defvar notmuch-folders nil "Deprecated name for what is now known as `notmuch-saved-searches'.") diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el index 6fbf82d..dcfbc4b 100644 --- a/emacs/notmuch-maildir-fcc.el +++ b/emacs/notmuch-maildir-fcc.el @@ -51,13 +51,13 @@ the database.path option in the notmuch configuration file). You will be prompted to create the directory if it does not exist yet when sending a mail." - :require 'notmuch-fcc-initialization - :group 'notmuch :type '(choice (const :tag "No FCC header" nil) (string :tag "A single folder") (repeat :tag "A folder based on the From header" - (cons regexp (string :tag "Folder"))))) + (cons regexp (string :tag "Folder")))) + :require 'notmuch-fcc-initialization + :group 'notmuch-send) (defun notmuch-fcc-initialization () "If notmuch-fcc-directories is set, diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el index 08e5b17..264a5b9 100644 --- a/emacs/notmuch-message.el +++ b/emacs/notmuch-message.el @@ -31,7 +31,7 @@ For example, if you wanted to add a \"replied\" tag and remove the \"inbox\" and \"todo\", you would set (\"replied\" \"-inbox\" \"-todo\"\)" :type 'list - :group 'notmuch) + :group 'notmuch-send) (defun notmuch-message-mark-replied () ;; get the in-reply-to header and parse it for the message id. diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index d8ab822..023645e 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -28,25 +28,26 @@ (defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook) "Hook run before sending messages." - :group 'notmuch - :type 'hook) + :type 'hook + :group 'notmuch-send + :group 'notmuch-hooks) (defcustom notmuch-mua-user-agent-function 'notmuch-mua-user-agent-full "Function used to generate a `User-Agent:' string. If this is `nil' then no `User-Agent:' will be generated." - :group 'notmuch :type '(choice (const :tag "No user agent string" nil) (const :tag "Full" notmuch-mua-user-agent-full) (const :tag "Notmuch" notmuch-mua-user-agent-notmuch) (const :tag "Emacs" notmuch-mua-user-agent-emacs) (function :tag "Custom user agent function" - :value notmuch-mua-user-agent-full))) + :value notmuch-mua-user-agent-full)) + :group 'notmuch-send) (defcustom notmuch-mua-hidden-headers '("^User-Agent:") "Headers that are added to the `message-mode' hidden headers list." - :group 'notmuch - :type '(repeat string)) + :type '(repeat string) + :group 'notmuch-send) ;; @@ -157,16 +158,16 @@ OTHER-ARGS are passed through to `message-mail'." If this variable is left unset, then a list will be constructed from the name and addresses configured in the notmuch configuration file." - :group 'notmuch - :type '(repeat string)) + :type '(repeat string) + :group 'notmuch-send) (defcustom notmuch-always-prompt-for-sender nil "Always prompt for the From: address when composing or forwarding a message. This is not taken into account when replying to a message, because in that case the From: header is already filled in by notmuch." - :group 'notmuch - :type 'boolean) + :type 'boolean + :group 'notmuch-send) (defvar notmuch-mua-sender-history nil) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 03c1f6b..fc13462 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -47,8 +47,8 @@ For an open message, all of these headers will be made visible according to `notmuch-message-headers-visible' or can be toggled with `notmuch-show-toggle-headers'. For a closed message, only the first header in the list will be visible." - :group 'notmuch - :type '(repeat string)) + :type '(repeat string) + :group 'notmuch-show) (defcustom notmuch-message-headers-visible t "Should the headers be visible by default? @@ -58,13 +58,13 @@ If this value is non-nil, then all of the headers defined in of each message. Otherwise, these headers will be hidden and `notmuch-show-toggle-headers' can be used to make the visible for any given message." - :group 'notmuch - :type 'boolean) + :type 'boolean + :group 'notmuch-show) (defcustom notmuch-show-relative-dates t "Display relative dates in the message summary line." - :group 'notmuch - :type 'boolean) + :type 'boolean + :group 'notmuch-show) (defvar notmuch-show-markup-headers-hook '(notmuch-show-colour-headers) "A list of functions called to decorate the headers listed in @@ -72,27 +72,29 @@ any given message." (defcustom notmuch-show-hook nil "Functions called after populating a `notmuch-show' buffer." - :group 'notmuch - :type 'hook) + :type 'hook + :group 'notmuch-show + :group 'notmuch-hooks) (defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-wrap-long-lines notmuch-wash-tidy-citations notmuch-wash-elide-blank-lines notmuch-wash-excerpt-citations) "Functions used to improve the display of text/plain parts." - :group 'notmuch :type 'hook :options '(notmuch-wash-convert-inline-patch-to-part notmuch-wash-wrap-long-lines notmuch-wash-tidy-citations notmuch-wash-elide-blank-lines - notmuch-wash-excerpt-citations)) + notmuch-wash-excerpt-citations) + :group 'notmuch-show + :group 'notmuch-hooks) ;; Mostly useful for debugging. (defcustom notmuch-show-all-multipart/alternative-parts t "Should all parts of multipart/alternative parts be shown?" - :group 'notmuch - :type 'boolean) + :type 'boolean + :group 'notmuch-show) (defcustom notmuch-show-indent-messages-width 1 "Width of message indentation in threads. @@ -101,14 +103,14 @@ Messages are shown indented according to their depth in a thread. This variable determines the width of this indentation measured in number of blanks. Defaults to `1', choose `0' to disable indentation." - :group 'notmuch - :type 'integer) + :type 'integer + :group 'notmuch-show) (defcustom notmuch-show-indent-multipart nil "Should the sub-parts of a multipart/* part be indented?" ;; dme: Not sure which is a good default. - :group 'notmuch - :type 'boolean) + :type 'boolean + :group 'notmuch-show) (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" diff --git a/emacs/notmuch.el b/emacs/notmuch.el index ef4dcc7..3602361 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -70,7 +70,7 @@ For example: (setq notmuch-search-result-format \(\(\"authors\" . \"%-40s\"\) \(\"subject\" . \"%s\"\)\)\)" :type '(alist :key-type (string) :value-type (string)) - :group 'notmuch) + :group 'notmuch-search) (defvar notmuch-query-history nil "Variable to store minibuffer history for notmuch queries") @@ -199,7 +199,8 @@ For a mouse binding, return nil." "List of functions to call when notmuch displays the search results." :type 'hook :options '(hl-line-mode) - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-hooks) (defvar notmuch-search-mode-map (let ((map (make-sparse-keymap))) @@ -307,27 +308,32 @@ For a mouse binding, return nil." '((((class color) (background light)) (:background "#f0f0f0")) (((class color) (background dark)) (:background "#303030"))) "Face for the single-line message summary in notmuch-show-mode." - :group 'notmuch) + :group 'notmuch-show + :group 'notmuch-faces) (defface notmuch-search-date '((t :inherit default)) "Face used in search mode for dates." - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defface notmuch-search-count '((t :inherit default)) "Face used in search mode for the count matching the query." - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defface notmuch-search-subject '((t :inherit default)) "Face used in search mode for subjects." - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defface notmuch-search-matching-authors '((t :inherit default)) "Face used in search mode for authors matching the query." - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defface notmuch-search-non-matching-authors '((((class color) @@ -339,7 +345,8 @@ For a mouse binding, return nil." (t (:italic t))) "Face used in search mode for authors not matching the query." - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defface notmuch-tag-face '((((class color) @@ -351,7 +358,8 @@ For a mouse binding, return nil." (t (:bold t))) "Face used in search mode face for tags." - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defun notmuch-search-mode () "Major mode displaying results of a notmuch search. @@ -502,7 +510,7 @@ the messages that are about to be tagged" :type 'hook :options '(hl-line-mode) - :group 'notmuch) + :group 'notmuch-hooks) (defcustom notmuch-after-tag-hook nil "Hooks that are run after tags of a message are modified. @@ -513,7 +521,7 @@ a list of strings of the form \"+TAG\" or \"-TAG\". the messages that were tagged" :type 'hook :options '(hl-line-mode) - :group 'notmuch) + :group 'notmuch-hooks) (defun notmuch-search-set-tags (tags) (save-excursion @@ -669,7 +677,8 @@ attributes overriding earlier. A message having both \"delete\" and \"unread\" tags with the above settings would have a green foreground and blue background." :type '(alist :key-type (string) :value-type (custom-face-edit)) - :group 'notmuch) + :group 'notmuch-search + :group 'notmuch-faces) (defun notmuch-search-color-line (start end line-tag-list) "Colorize lines in `notmuch-show' based on tags." @@ -1004,7 +1013,7 @@ Note that the recommended way of achieving the same is using :type '(choice (const :tag "notmuch new" nil) (const :tag "Disabled" "") (string :tag "Custom script")) - :group 'notmuch) + :group 'notmuch-external) (defun notmuch-poll () "Run \"notmuch new\" or an external script to import mail. -- cgit v1.2.3 From ad6d0d5e12114815bcf6ba3499f94a5ff13809b4 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Wed, 18 Jan 2012 15:58:50 -0500 Subject: News for tag exclusion --- NEWS | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/NEWS b/NEWS index 1e561a9..6afa912 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,13 @@ Reply to sender to all. The feature is available through the new command line option --reply-to=(all|sender). +Tag exclusion + + Tags can be automatically excluded from search results unless they + appear explicitly in a query. By default, notmuch excludes the tags + deleted and spam. This can be changed using the new config setting + search.auto_exclude_tags. + Emacs Interface --------------- @@ -21,6 +28,14 @@ Reply to sender and search modes, 'r' has been bound to reply to sender, replacing reply to all, which now has key binding 'R'. +Library changes +--------------- + +New functions + + notmuch_query_add_tag_exclude supports the new tag exclusion + feature. + Notmuch 0.11 (2012-01-13) ========================= -- cgit v1.2.3 From a56e6603c604cbe010a520c9084e0ad2895755d9 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Wed, 18 Jan 2012 15:56:45 -0500 Subject: config: Better formatting for search section comment Since "auto_exclude_tags" is long and its description is multi-line, start the description on the next line and indent it consistently. --- notmuch-config.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/notmuch-config.c b/notmuch-config.c index 3d4d5b9..8dcfe86 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -89,9 +89,10 @@ static const char search_config_comment[] = "\n" " The following option is supported here:\n" "\n" - "\tauto_exclude_tags A ;-separated list of tags that will be\n" - "\t excluded from search results by default. Using an excluded tag\n" - "\t in a query will override that exclusion.\n"; + "\tauto_exclude_tags\n" + "\t\tA ;-separated list of tags that will be excluded from\n" + "\t\tsearch results by default. Using an excluded tag in a\n" + "\t\tquery will override that exclusion.\n"; struct _notmuch_config { char *filename; -- cgit v1.2.3 From 046f5dded2a113d80e55288aaa1659a6843dafa3 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 16 Jan 2012 11:56:40 +0100 Subject: emacs: globally replace non-branching "(if (not ..." with "(unless ..." Less code, same results, without sacrificing readability. --- emacs/notmuch-address.el | 6 +++--- emacs/notmuch-hello.el | 22 +++++++++++----------- emacs/notmuch-show.el | 12 ++++++------ emacs/notmuch.el | 8 ++++---- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index 2e8b840..2bf762b 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -38,9 +38,9 @@ line." (defvar notmuch-address-history nil) (defun notmuch-address-message-insinuate () - (if (not (memq notmuch-address-message-alist-member message-completion-alist)) - (setq message-completion-alist - (push notmuch-address-message-alist-member message-completion-alist)))) + (unless (memq notmuch-address-message-alist-member message-completion-alist) + (setq message-completion-alist + (push notmuch-address-message-alist-member message-completion-alist)))) (defun notmuch-address-options (original) (process-lines notmuch-address-command original)) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index bff95ac..ba48cd6 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -320,8 +320,8 @@ should be. Returns a cons cell `(tags-per-line width)'." ;; If the last line was not full (and hence did not include a ;; carriage return), insert one now. - (if (not (eq (% count tags-per-line) 0)) - (widget-insert "\n")) + (unless (eq (% count tags-per-line) 0) + (widget-insert "\n")) found-target-pos)) (defun notmuch-hello-goto-search () @@ -404,7 +404,7 @@ Complete list of currently available key bindings: ; Jump through a hoop to get this value from the deprecated variable ; name (`notmuch-folders') or from the default value. - (if (not notmuch-saved-searches) + (unless notmuch-saved-searches (setq notmuch-saved-searches (notmuch-saved-searches))) (if no-display @@ -570,18 +570,18 @@ Complete list of currently available key bindings: (widget-insert "\n\n") (let ((start (point))) (setq found-target-pos (notmuch-hello-insert-tags alltags-alist widest target)) - (if (not final-target-pos) - (setq final-target-pos found-target-pos)) + (unless final-target-pos + (setq final-target-pos found-target-pos)) (indent-rigidly start (point) notmuch-hello-indent))) (widget-insert "\n") - (if (not notmuch-show-all-tags-list) - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list t) - (notmuch-hello-update)) - "Show all tags"))) + (unless notmuch-show-all-tags-list + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (setq notmuch-show-all-tags-list t) + (notmuch-hello-update)) + "Show all tags"))) (let ((start (point))) (widget-insert "\n\n") diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index fc13462..92d3811 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -662,8 +662,8 @@ current buffer, if possible." ;; part, so we make sure that we're down at the end. (goto-char (point-max)) ;; Ensure that the part ends with a carriage return. - (if (not (bolp)) - (insert "\n"))) + (unless (bolp) + (insert "\n"))) (defun notmuch-show-insert-body (msg body depth) "Insert the body BODY at depth DEPTH in the current thread." @@ -743,8 +743,8 @@ current buffer, if possible." (setq body-start (point-marker)) (notmuch-show-insert-body msg (plist-get msg :body) depth) ;; Ensure that the body ends with a newline. - (if (not (bolp)) - (insert "\n")) + (unless (bolp) + (insert "\n")) (setq body-end (point-marker)) (setq content-end (point-marker)) @@ -885,8 +885,8 @@ buffer." (run-hooks 'notmuch-show-hook)) ;; Move straight to the first open message - (if (not (notmuch-show-message-visible-p)) - (notmuch-show-next-open-message)) + (unless (notmuch-show-message-visible-p) + (notmuch-show-next-open-message)) ;; Set the header line to the subject of the first open message. (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-subject))) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 3602361..da75faf 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -651,8 +651,8 @@ This function advances the next thread when finished." (if notmuch-search-process-filter-data (insert (concat "Error: Unexpected output from notmuch search:\n" notmuch-search-process-filter-data))) (insert "End of search results.") - (if (not (= exit-status 0)) - (insert (format " (process returned %d)" exit-status))) + (unless (= exit-status 0) + (insert (format " (process returned %d)" exit-status))) (insert "\n") (if (and atbob (not (string= notmuch-search-target-thread "found"))) @@ -1022,8 +1022,8 @@ Invokes `notmuch-poll-script', \"notmuch new\", or does nothing depending on the value of `notmuch-poll-script'." (interactive) (if (stringp notmuch-poll-script) - (if (not (string= notmuch-poll-script "")) - (call-process notmuch-poll-script nil nil)) + (unless (string= notmuch-poll-script "") + (call-process notmuch-poll-script nil nil)) (call-process notmuch-command nil nil nil "new"))) (defun notmuch-search-poll-and-refresh-view () -- cgit v1.2.3 From d2a1140c44f0cdaf16c06730b7311cbe222bc2da Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Mon, 16 Jan 2012 14:13:22 +0000 Subject: emacs: Truncate lines and do not enable visual-line-mode in notmuch-show buffers. Enable the truncation of lines in `notmuch-show-mode' to avoid visual noise caused by the wrapping of the header lines. Don't enable `visual-line-mode' because it disables line truncation. The benefits of `visual-line-mode' were that it wrapped long lines in received messages. With `notmuch-wash-wrap-long-lines' now default behaviour, this is no longer required. --- emacs/notmuch-show.el | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 92d3811..fbf3f9b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -879,9 +879,6 @@ buffer." (jit-lock-register #'notmuch-show-buttonise-links) - ;; Act on visual lines rather than logical lines. - (visual-line-mode t) - (run-hooks 'notmuch-show-hook)) ;; Move straight to the first open message @@ -993,7 +990,8 @@ All currently available key bindings: (use-local-map notmuch-show-mode-map) (setq major-mode 'notmuch-show-mode mode-name "notmuch-show") - (setq buffer-read-only t)) + (setq buffer-read-only t + truncate-lines t)) (defun notmuch-show-move-to-message-top () (goto-char (notmuch-show-message-top))) -- cgit v1.2.3 From 8ae753f30fb90419c50b9bb1fc87618dd1663bc9 Mon Sep 17 00:00:00 2001 From: Ethan Glasser-Camp Date: Mon, 16 Jan 2012 13:52:47 -0500 Subject: Document external dependencies in the test suite Add an explicit note to the README explaining what programs are necessary and the perhaps-surprising behavior of skipping tests if they aren't present. Signed-off-by: Ethan Glasser-Camp --- test/README | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/README b/test/README index bde6db0..44ff653 100644 --- a/test/README +++ b/test/README @@ -6,6 +6,19 @@ When fixing bugs or enhancing notmuch, you are strongly encouraged to add tests in this directory to cover what you are trying to fix or enhance. +Prerequisites +------------- +Some tests require external dependencies to run. Without them, they +will be skipped, or (rarely) marked failed. Please install these, so +that you know if you break anything. + + - dtach(1) + - emacs(1) + - emacsclient(1) + - gdb(1) + - gpg(1) + - python(1) + Running Tests ------------- The easiest way to run tests is to say "make test", (or simply run the -- cgit v1.2.3 From 05f4904616b95a17332d7573e44a4aad2dc4033e Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 18 Jan 2012 08:00:21 +0000 Subject: emacs: Improved printing support. Add various functions to print notmuch messages and tie them together with a simple frontend. Add a binding ('#') in `notmuch-show-mode' to print the current message. one trailing space removed by db. --- emacs/Makefile.local | 3 +- emacs/notmuch-print.el | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ emacs/notmuch-show.el | 53 +++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 emacs/notmuch-print.el diff --git a/emacs/Makefile.local b/emacs/Makefile.local index 0c58b82..4fee0e8 100644 --- a/emacs/Makefile.local +++ b/emacs/Makefile.local @@ -13,7 +13,8 @@ emacs_sources := \ $(dir)/notmuch-maildir-fcc.el \ $(dir)/notmuch-message.el \ $(dir)/notmuch-crypto.el \ - $(dir)/coolj.el + $(dir)/coolj.el \ + $(dir)/notmuch-print.el emacs_images := \ $(srcdir)/$(dir)/notmuch-logo.png diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el new file mode 100644 index 0000000..f96ccbe --- /dev/null +++ b/emacs/notmuch-print.el @@ -0,0 +1,85 @@ +;; notmuch-print.el --- printing messages from notmuch. +;; +;; Copyright © David Edmondson +;; +;; This file is part of Notmuch. +;; +;; Notmuch is free software: you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; Notmuch is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Notmuch. If not, see . +;; +;; Authors: David Edmondson + +(defcustom notmuch-print-mechanism 'notmuch-print-lpr + "How should printing be done?" + :group 'notmuch + :type '(choice + (function :tag "Use lpr" notmuch-print-lpr) + (function :tag "Use ps-print" notmuch-print-ps-print) + (function :tag "Use ps-print then evince" notmuch-print-ps-print/evince) + (function :tag "Use muttprint" notmuch-print-muttprint) + (function :tag "Use muttprint then evince" notmuch-print-muttprint/evince) + (function :tag "Using a custom function"))) + +;; Utility functions: + +(defun notmuch-print-run-evince (file) + "View FILE using 'evince'." + (start-process "evince" nil "evince" file)) + +(defun notmuch-print-run-muttprint (&optional output) + "Pass the contents of the current buffer to 'muttprint'. + +Optional OUTPUT allows passing a list of flags to muttprint." + (apply #'call-process-region (point-min) (point-max) + ;; Reads from stdin. + "muttprint" + nil nil nil + ;; Show the tags. + "--printed-headers" "Date_To_From_CC_Newsgroups_*Subject*_/Tags/" + output)) + +;; User-visible functions: + +(defun notmuch-print-lpr (msg) + "Print a message buffer using lpr." + (lpr-buffer)) + +(defun notmuch-print-ps-print (msg) + "Print a message buffer using the ps-print package." + (let ((subject (plist-get (notmuch-show-get-prop :headers msg) :Subject))) + (rename-buffer subject t) + (ps-print-buffer))) + +(defun notmuch-print-ps-print/evince (msg) + "Preview a message buffer using ps-print and evince." + (let ((ps-file (make-temp-file "notmuch")) + (subject (plist-get (notmuch-show-get-prop :headers msg) :Subject))) + (rename-buffer subject t) + (ps-print-buffer ps-file) + (notmuch-print-run-evince ps-file))) + +(defun notmuch-print-muttprint (msg) + "Print a message using muttprint." + (notmuch-print-run-muttprint)) + +(defun notmuch-print-muttprint/evince (msg) + "Preview a message buffer using muttprint and evince." + (let ((ps-file (make-temp-file "notmuch"))) + (notmuch-print-run-muttprint (list "--printer" (concat "TO_FILE:" ps-file))) + (notmuch-print-run-evince ps-file))) + +(defun notmuch-print-message (msg) + "Print a message using the user-selected mechanism." + (funcall notmuch-print-mechanism msg)) + +(provide 'notmuch-print) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index fbf3f9b..8b0d3fe 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -34,6 +34,7 @@ (require 'notmuch-wash) (require 'notmuch-mua) (require 'notmuch-crypto) +(require 'notmuch-print) (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-fontify-headers "notmuch" nil) @@ -188,6 +189,52 @@ indentation." mm-handle (> (notmuch-count-attachments mm-handle) 1)))) (message "Done")) +(defun notmuch-show-with-message-as-text (fn) + "Apply FN to a text representation of the current message. + +FN is called with one argument, the message properties. It should +operation on the contents of the current buffer." + + ;; Remake the header to ensure that all information is available. + (let* ((to (notmuch-show-get-to)) + (cc (notmuch-show-get-cc)) + (from (notmuch-show-get-from)) + (subject (notmuch-show-get-subject)) + (date (notmuch-show-get-date)) + (tags (notmuch-show-get-tags)) + (depth (notmuch-show-get-depth)) + + (header (concat + "Subject: " subject "\n" + "To: " to "\n" + (if (not (string= cc "")) + (concat "Cc: " cc "\n") + "") + "From: " from "\n" + "Date: " date "\n" + (if tags + (concat "Tags: " + (mapconcat #'identity tags ", ") "\n") + ""))) + (all (buffer-substring (notmuch-show-message-top) + (notmuch-show-message-bottom))) + + (props (notmuch-show-get-message-properties))) + (with-temp-buffer + (insert all) + (indent-rigidly (point-min) (point-max) (- depth)) + ;; Remove the original header. + (goto-char (point-min)) + (re-search-forward "^$" (point-max) nil) + (delete-region (point-min) (point)) + (insert header) + (funcall fn props)))) + +(defun notmuch-show-print-message () + "Print the current message." + (interactive) + (notmuch-show-with-message-as-text 'notmuch-print-message)) + (defun notmuch-show-fontify-header () (let ((face (cond ((looking-at "[Tt]o:") @@ -763,6 +810,8 @@ current buffer, if possible." (overlay-put headers-overlay 'priority 10)) (overlay-put (make-overlay body-start body-end) 'invisible message-invis-spec) + (plist-put msg :depth depth) + ;; Save the properties for this message. Currently this saves the ;; entire message (augmented it with other stuff), which seems ;; like overkill. We might save a reduced subset (for example, not @@ -953,6 +1002,7 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map " " 'notmuch-show-advance-and-archive) (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all) (define-key map (kbd "RET") 'notmuch-show-toggle-message) + (define-key map "#" 'notmuch-show-print-message) map) "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) @@ -1113,6 +1163,9 @@ Some useful entries are: (defun notmuch-show-get-to () (notmuch-show-get-header :To)) +(defun notmuch-show-get-depth () + (notmuch-show-get-prop :depth)) + (defun notmuch-show-set-tags (tags) "Set the tags of the current message." (notmuch-show-set-prop :tags tags) -- cgit v1.2.3 From 3a602dc27aa7a830c0bf00838dfdbb70165858d5 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Thu, 19 Jan 2012 20:13:05 +0100 Subject: emacs: invert relation between 'notmuch-send and 'message customization groups 'message contains options relevant to 'notmuch-send, not the other way around. Thanks to Austin for suggesting `custom-add-to-group'. id:"20120118184408.GD16740@mit.edu" --- emacs/notmuch-lib.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 9242537..e33e69a 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -42,8 +42,9 @@ (defgroup notmuch-send nil "Sending messages from Notmuch." - :group 'notmuch - :group 'message) + :group 'notmuch) + +(custom-add-to-group 'notmuch-send 'message 'custom-group) (defgroup notmuch-crypto nil "Processing and display of cryptographic MIME parts." -- cgit v1.2.3 From 18947b95cd1668d1b98f7ea4196e97b050599f7d Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 19 Jan 2012 17:29:18 -0500 Subject: show: Handle read and write errors For showing a message in raw format, rather than silently succeeding when a read or a write fails (or, probably, looping if a read fails), try to print an error message and exit with a non-zero status. This silences one of the buildbot warnings about unused results. While my libc lacks the declarations that trigger these warnings, this can be tested by adding the following to notmuch.h: __attribute__((warn_unused_result)) size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); --- notmuch-show.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/notmuch-show.c b/notmuch-show.c index d14dac9..c674e25 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -883,7 +883,17 @@ do_show_single (void *ctx, while (!feof (file)) { size = fread (buf, 1, sizeof (buf), file); - (void) fwrite (buf, size, 1, stdout); + if (ferror (file)) { + fprintf (stderr, "Error: Read failed from %s\n", filename); + fclose (file); + return 1; + } + + if (fwrite (buf, size, 1, stdout) != 1) { + fprintf (stderr, "Error: Write failed\n"); + fclose (file); + return 1; + } } fclose (file); -- cgit v1.2.3 From a9a9e374e2567caad8601d1781a3b0af8a3dde13 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 19 Jan 2012 17:29:19 -0500 Subject: Silence buildbot warnings about unused results This ignores the results of the two writes in sigint handlers even harder than before. While my libc lacks the declarations that trigger these warnings, this can be tested by adding the following to notmuch.h: __attribute__((warn_unused_result)) ssize_t write(int fd, const void *buf, size_t count); --- compat/compat.h | 8 ++++++++ notmuch-new.c | 6 +++++- notmuch-tag.c | 7 ++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/compat/compat.h b/compat/compat.h index 7767fe8..b2e2736 100644 --- a/compat/compat.h +++ b/compat/compat.h @@ -46,6 +46,14 @@ getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp); char* strcasestr(const char *haystack, const char *needle); #endif /* !HAVE_STRCASESTR */ +/* Silence gcc warnings about unused results. These warnings exist + * for a reason; any use of this needs to be justified. */ +#ifdef __GNUC__ +#define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void)(__z = __z); }) +#else /* !__GNUC__ */ +#define IGNORE_RESULT(x) x +#endif /* __GNUC__ */ + #ifdef __cplusplus } #endif diff --git a/notmuch-new.c b/notmuch-new.c index 3512de7..a569a54 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -67,7 +67,11 @@ handle_sigint (unused (int sig)) { static char msg[] = "Stopping... \n"; - (void) write(2, msg, sizeof(msg)-1); + /* This write is "opportunistic", so it's okay to ignore the + * result. It is not required for correctness, and if it does + * fail or produce a short write, we want to get out of the signal + * handler as quickly as possible, not retry it. */ + IGNORE_RESULT (write (2, msg, sizeof(msg)-1)); interrupted = 1; } diff --git a/notmuch-tag.c b/notmuch-tag.c index 292c5da..44fd61f 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -26,7 +26,12 @@ static void handle_sigint (unused (int sig)) { static char msg[] = "Stopping... \n"; - (void) write(2, msg, sizeof(msg)-1); + + /* This write is "opportunistic", so it's okay to ignore the + * result. It is not required for correctness, and if it does + * fail or produce a short write, we want to get out of the signal + * handler as quickly as possible, not retry it. */ + IGNORE_RESULT (write (2, msg, sizeof(msg)-1)); interrupted = 1; } -- cgit v1.2.3 From d3aa6848da4519f6229426b1d0432afacf748f8b Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Fri, 20 Jan 2012 00:52:14 +0100 Subject: emacs: add invisible dot instead of space at the end of notmuch-hello search box This makes `show-trailing-whitespace' happy, i.e. it does not mark the whole search box line as trailing spaces. Since the dot is invisible, this change makes no visible difference for `notmuch-hello'. Edited-by: Pieter Praet to fix the tests. --- emacs/notmuch-hello.el | 9 ++++++--- test/emacs.expected-output/notmuch-hello | 2 +- test/emacs.expected-output/notmuch-hello-no-saved-searches | 2 +- test/emacs.expected-output/notmuch-hello-with-empty | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index ba48cd6..1a61768 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -511,9 +511,12 @@ Complete list of currently available key bindings: (length "Search: "))) :action (lambda (widget &rest ignore) (notmuch-hello-search (widget-value widget)))) - ;; add an invisible space to make `widget-end-of-line' ignore - ;; trailine spaces in the search widget field - (widget-insert " ") + ;; Add an invisible dot to make `widget-end-of-line' ignore + ;; trailing spaces in the search widget field. A dot is used + ;; instead of a space to make `show-trailing-whitespace' + ;; happy, i.e. avoid it marking the whole line as trailing + ;; spaces. + (widget-insert ".") (put-text-property (1- (point)) (point) 'invisible t) (widget-insert "\n") diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index de57de2..196112e 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -4,7 +4,7 @@ Saved searches: [edit] 52 inbox 52 unread -Search: +Search: . [Show all tags] diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index f1fc4d6..f4cfe49 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -1,6 +1,6 @@ Welcome to notmuch. You have 52 messages. -Search: +Search: . [Show all tags] diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index dd8728b..a860a72 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -4,7 +4,7 @@ Saved searches: [edit] 52 inbox 52 unread 0 empty -Search: +Search: . [Show all tags] -- cgit v1.2.3 From 00b5623d1a21d886b564d031e30749e5d02e4ae6 Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Fri, 20 Jan 2012 10:39:24 +0100 Subject: Add compatibility with gmime 2.6 There are lots of API changes in gmime 2.6 crypto handling. By adding preprocessor directives, it is however possible to add gmime 2.6 compatibility while preserving compatibility with gmime 2.4 too. This is mostly based on id:"8762i8hrb9.fsf@bookbinder.fernseed.info". This was tested against both gmime 2.6.4 and 2.4.31. With gmime 2.4.31, the crypto tests all work fine (as expected). With gmime 2.6.4, one crypto test is currently broken (signature verification with signer key unavailable), most likely because of a bug in gmime which will hopefully be fixed in a future version. --- mime-node.c | 57 ++++++++++++++++++++++++++++++++-- notmuch-client.h | 30 ++++++++++++++++-- notmuch-reply.c | 7 +++++ notmuch-show.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ show-message.c | 4 +++ test/crypto | 2 ++ 6 files changed, 191 insertions(+), 4 deletions(-) diff --git a/mime-node.c b/mime-node.c index d26bb44..27077f7 100644 --- a/mime-node.c +++ b/mime-node.c @@ -33,7 +33,11 @@ typedef struct mime_node_context { GMimeMessage *mime_message; /* Context provided by the caller. */ +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext *cryptoctx; +#else GMimeCipherContext *cryptoctx; +#endif notmuch_bool_t decrypt; } mime_node_context_t; @@ -57,8 +61,12 @@ _mime_node_context_free (mime_node_context_t *res) notmuch_status_t mime_node_open (const void *ctx, notmuch_message_t *message, - GMimeCipherContext *cryptoctx, notmuch_bool_t decrypt, - mime_node_t **root_out) +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext *cryptoctx, +#else + GMimeCipherContext *cryptoctx, +#endif + notmuch_bool_t decrypt, mime_node_t **root_out) { const char *filename = notmuch_message_get_filename (message); mime_node_context_t *mctx; @@ -112,12 +120,21 @@ DONE: return status; } +#ifdef GMIME_ATLEAST_26 +static int +_signature_list_free (GMimeSignatureList **proxy) +{ + g_object_unref (*proxy); + return 0; +} +#else static int _signature_validity_free (GMimeSignatureValidity **proxy) { g_mime_signature_validity_free (*proxy); return 0; } +#endif static mime_node_t * _mime_node_create (const mime_node_t *parent, GMimeObject *part) @@ -165,11 +182,25 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part); node->decrypt_attempted = TRUE; +#ifdef GMIME_ATLEAST_26 + GMimeDecryptResult *decrypt_result = NULL; + node->decrypted_child = g_mime_multipart_encrypted_decrypt + (encrypteddata, node->ctx->cryptoctx, &decrypt_result, &err); +#else node->decrypted_child = g_mime_multipart_encrypted_decrypt (encrypteddata, node->ctx->cryptoctx, &err); +#endif if (node->decrypted_child) { node->decrypt_success = node->verify_attempted = TRUE; +#ifdef GMIME_ATLEAST_26 + /* This may be NULL if the part is not signed. */ + node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result); + if (node->sig_list) + g_object_ref (node->sig_list); + g_object_unref (decrypt_result); +#else node->sig_validity = g_mime_multipart_encrypted_get_signature_validity (encrypteddata); +#endif } else { fprintf (stderr, "Failed to decrypt part: %s\n", (err ? err->message : "no error explanation given")); @@ -182,6 +213,15 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) "(must be exactly 2)\n", node->nchildren); } else { +#ifdef GMIME_ATLEAST_26 + node->sig_list = g_mime_multipart_signed_verify + (GMIME_MULTIPART_SIGNED (part), node->ctx->cryptoctx, &err); + node->verify_attempted = TRUE; + + if (!node->sig_list) + fprintf (stderr, "Failed to verify signed part: %s\n", + (err ? err->message : "no error explanation given")); +#else /* For some reason the GMimeSignatureValidity returned * here is not a const (inconsistent with that * returned by @@ -200,12 +240,25 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) *proxy = sig_validity; talloc_set_destructor (proxy, _signature_validity_free); } +#endif } } +#ifdef GMIME_ATLEAST_26 + /* sig_list may be created in both above cases, so we need to + * cleanly handle it here. */ + if (node->sig_list) { + GMimeSignatureList **proxy = talloc (node, GMimeSignatureList *); + *proxy = node->sig_list; + talloc_set_destructor (proxy, _signature_list_free); + } +#endif + +#ifndef GMIME_ATLEAST_26 if (node->verify_attempted && !node->sig_validity) fprintf (stderr, "Failed to verify signed part: %s\n", (err ? err->message : "no error explanation given")); +#endif if (err) g_error_free (err); diff --git a/notmuch-client.h b/notmuch-client.h index 62ede28..9c1d383 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -30,6 +30,14 @@ #include +/* GMIME_CHECK_VERSION in gmime 2.4 is not usable from the + * preprocessor (it calls a runtime function). But since + * GMIME_MAJOR_VERSION and friends were added in gmime 2.6, we can use + * these to check the version number. */ +#ifdef GMIME_MAJOR_VERSION +#define GMIME_ATLEAST_26 +#endif + #include "notmuch.h" /* This is separate from notmuch-private.h because we're trying to @@ -69,7 +77,11 @@ typedef struct notmuch_show_format { void (*part_start) (GMimeObject *part, int *part_count); void (*part_encstatus) (int status); +#ifdef GMIME_ATLEAST_26 + void (*part_sigstatus) (GMimeSignatureList* siglist); +#else void (*part_sigstatus) (const GMimeSignatureValidity* validity); +#endif void (*part_content) (GMimeObject *part); void (*part_end) (GMimeObject *part); const char *part_sep; @@ -83,7 +95,11 @@ typedef struct notmuch_show_params { int entire_thread; int raw; int part; +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext* cryptoctx; +#else GMimeCipherContext* cryptoctx; +#endif int decrypt; } notmuch_show_params_t; @@ -290,11 +306,17 @@ typedef struct mime_node { /* True if signature verification on this part was attempted. */ notmuch_bool_t verify_attempted; +#ifdef GMIME_ATLEAST_26 + /* The list of signatures for signed or encrypted containers. If + * there are no signatures, this will be NULL. */ + GMimeSignatureList* sig_list; +#else /* For signed or encrypted containers, the validity of the * signature. May be NULL if signature verification failed. If * there are simply no signatures, this will be non-NULL with an * empty signers list. */ const GMimeSignatureValidity *sig_validity; +#endif /* Internal: Context inherited from the root iterator. */ struct mime_node_context *ctx; @@ -319,8 +341,12 @@ typedef struct mime_node { */ notmuch_status_t mime_node_open (const void *ctx, notmuch_message_t *message, - GMimeCipherContext *cryptoctx, notmuch_bool_t decrypt, - mime_node_t **node_out); +#ifdef GMIME_ATLEAST_26 + GMimeCryptoContext *cryptoctx, +#else + GMimeCipherContext *cryptoctx, +#endif + notmuch_bool_t decrypt, mime_node_t **node_out); /* Return a new MIME node for the requested child part of parent. * parent will be used as the talloc context for the returned child diff --git a/notmuch-reply.c b/notmuch-reply.c index 0f682db..bf67960 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -688,15 +688,22 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) reply_format_func = notmuch_reply_format_default; if (decrypt) { +#ifdef GMIME_ATLEAST_26 + /* TODO: GMimePasswordRequestFunc */ + params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); +#else GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); params.cryptoctx = g_mime_gpg_context_new (session, "gpg"); +#endif if (params.cryptoctx) { g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); params.decrypt = TRUE; } else { fprintf (stderr, "Failed to construct gpg context.\n"); } +#ifndef GMIME_ATLEAST_26 g_object_unref (session); +#endif } config = notmuch_config_open (ctx, NULL, NULL); diff --git a/notmuch-show.c b/notmuch-show.c index c674e25..43ee211 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -76,7 +76,11 @@ static void format_part_encstatus_json (int status); static void +#ifdef GMIME_ATLEAST_26 +format_part_sigstatus_json (GMimeSignatureList* siglist); +#else format_part_sigstatus_json (const GMimeSignatureValidity* validity); +#endif static void format_part_content_json (GMimeObject *part); @@ -486,6 +490,21 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out) g_object_unref(stream_filter); } +#ifdef GMIME_ATLEAST_26 +static const char* +signature_status_to_string (GMimeSignatureStatus x) +{ + switch (x) { + case GMIME_SIGNATURE_STATUS_GOOD: + return "good"; + case GMIME_SIGNATURE_STATUS_BAD: + return "bad"; + case GMIME_SIGNATURE_STATUS_ERROR: + return "error"; + } + return "unknown"; +} +#else static const char* signer_status_to_string (GMimeSignerStatus x) { @@ -501,6 +520,7 @@ signer_status_to_string (GMimeSignerStatus x) } return "unknown"; } +#endif static void format_part_start_text (GMimeObject *part, int *part_count) @@ -592,6 +612,73 @@ format_part_encstatus_json (int status) printf ("}]"); } +#ifdef GMIME_ATLEAST_26 +static void +format_part_sigstatus_json (GMimeSignatureList *siglist) +{ + printf (", \"sigstatus\": ["); + + if (!siglist) { + printf ("]"); + return; + } + + void *ctx_quote = talloc_new (NULL); + int i; + for (i = 0; i < g_mime_signature_list_length (siglist); i++) { + GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i); + + if (i > 0) + printf (", "); + + printf ("{"); + + /* status */ + GMimeSignatureStatus status = g_mime_signature_get_status (signature); + printf ("\"status\": %s", + json_quote_str (ctx_quote, + signature_status_to_string (status))); + + GMimeCertificate *certificate = g_mime_signature_get_certificate (signature); + if (status == GMIME_SIGNATURE_STATUS_GOOD) { + if (certificate) + printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, g_mime_certificate_get_fingerprint (certificate))); + /* these dates are seconds since the epoch; should we + * provide a more human-readable format string? */ + time_t created = g_mime_signature_get_created (signature); + if (created != -1) + printf (", \"created\": %d", (int) created); + time_t expires = g_mime_signature_get_expires (signature); + if (expires > 0) + printf (", \"expires\": %d", (int) expires); + /* output user id only if validity is FULL or ULTIMATE. */ + /* note that gmime is using the term "trust" here, which + * is WRONG. It's actually user id "validity". */ + if (certificate) { + const char *name = g_mime_certificate_get_name (certificate); + GMimeCertificateTrust trust = g_mime_certificate_get_trust (certificate); + if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)) + printf (", \"userid\": %s", json_quote_str (ctx_quote, name)); + } + } else if (certificate) { + const char *key_id = g_mime_certificate_get_key_id (certificate); + if (key_id) + printf (", \"keyid\": %s", json_quote_str (ctx_quote, key_id)); + } + + GMimeSignatureError errors = g_mime_signature_get_errors (signature); + if (errors != GMIME_SIGNATURE_ERROR_NONE) { + printf (", \"errors\": %d", errors); + } + + printf ("}"); + } + + printf ("]"); + + talloc_free (ctx_quote); +} +#else static void format_part_sigstatus_json (const GMimeSignatureValidity* validity) { @@ -652,6 +739,7 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity) talloc_free (ctx_quote); } +#endif static void format_part_content_json (GMimeObject *part) @@ -1000,13 +1088,20 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { if (params.cryptoctx == NULL) { +#ifdef GMIME_ATLEAST_26 + /* TODO: GMimePasswordRequestFunc */ + if (NULL == (params.cryptoctx = g_mime_gpg_context_new(NULL, "gpg"))) +#else GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL); if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) +#endif fprintf (stderr, "Failed to construct gpg context.\n"); else g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE); +#ifndef GMIME_ATLEAST_26 g_object_unref (session); session = NULL; +#endif } if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) params.decrypt = 1; diff --git a/show-message.c b/show-message.c index 8768889..83ecf81 100644 --- a/show-message.c +++ b/show-message.c @@ -48,7 +48,11 @@ show_message_part (mime_node_t *node, format->part_encstatus (node->decrypt_success); if (node->verify_attempted && format->part_sigstatus) +#ifdef GMIME_ATLEAST_26 + format->part_sigstatus (node->sig_list); +#else format->part_sigstatus (node->sig_validity); +#endif format->part_content (part); diff --git a/test/crypto b/test/crypto index 0af4aa8..446a58b 100755 --- a/test/crypto +++ b/test/crypto @@ -104,6 +104,8 @@ test_expect_equal \ "$expected" test_begin_subtest "signature verification with signer key unavailable" +# this is broken with current versions of gmime-2.6 +(ldd $(which notmuch) | grep -Fq gmime-2.6) && test_subtest_known_broken # move the gnupghome temporarily out of the way mv "${GNUPGHOME}"{,.bak} output=$(notmuch show --format=json --verify subject:"test signed message 001" \ -- cgit v1.2.3 From 6d0fcea4e4fcfb803822741bbed762d499f9a055 Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Fri, 20 Jan 2012 10:39:25 +0100 Subject: Update NEWS and INSTALL about gmime 2.6 --- INSTALL | 12 ++++++------ NEWS | 9 +++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/INSTALL b/INSTALL index e51b397..bc98f1d 100644 --- a/INSTALL +++ b/INSTALL @@ -20,8 +20,8 @@ configure stage. Dependencies ------------ -Notmuch depends on three libraries: Xapian, GMime 2.4, and Talloc -which are each described below: +Notmuch depends on three libraries: Xapian, GMime 2.4 or 2.6, and +Talloc which are each described below: Xapian ------ @@ -39,14 +39,14 @@ which are each described below: reading mail while notmuch would wait for Xapian when removing the "inbox" and "unread" tags from messages in a thread. - GMime 2.4 - --------- - GMime 2.4 provides decoding of MIME email messages for Notmuch. + GMime 2.4 or 2.6 + ---------------- + GMime provides decoding of MIME email messages for Notmuch. Without GMime, Notmuch would not be able to extract and index the actual text from email message encoded as BASE64, etc. - GMime 2.4 is available from http://spruce.sourceforge.net/gmime/ + GMime is available from http://spruce.sourceforge.net/gmime/ Talloc ------ diff --git a/NEWS b/NEWS index 6afa912..e78472c 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,15 @@ New functions notmuch_query_add_tag_exclude supports the new tag exclusion feature. +Build fixes +----------- + +Compatibility with GMime 2.6 + + It is now possible to build notmuch against both GMime 2.4 and 2.6. + However they may be some issues in PGP signature verification + because of a bug in current versions of GMime 2.6. + Notmuch 0.11 (2012-01-13) ========================= -- cgit v1.2.3 From 85665a2955e729eeab1089282ac3f352129276a7 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Fri, 20 Jan 2012 09:44:06 +0000 Subject: Make buttons for attachments allow viewing as well as saving Define a keymap for attachment buttons to allow multiple actions. Define 3 possible actions: save attachment: exactly as currently, view attachment: uses mailcap entry, view attachment with user chosen program Keymap on a button is: s for save, v for view and o for view with other program. Default (i.e. enter or mouse button) is save but this is configurable in notmuch customize. One implementation detail: the view attachment function forces all attachments to be "displayed" using mailcap even if emacs could display them itself. Thus, for example, text/html appears in a browser and text/plain asks whether to save (on a standard debian setup) --- emacs/notmuch-show.el | 116 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 24 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 8b0d3fe..3fb13ab 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -113,6 +113,16 @@ indentation." :type 'boolean :group 'notmuch-show) +(defcustom notmuch-show-part-button-default-action 'notmuch-show-save-part + "Default part header button action (on ENTER or mouse click)." + :group 'notmuch-show + :type '(choice (const :tag "Save part" + notmuch-show-save-part) + (const :tag "View part" + notmuch-show-view-part) + (const :tag "View interactively" + notmuch-show-interactively-view-part))) + (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion @@ -330,10 +340,21 @@ message at DEPTH in the current thread." (run-hooks 'notmuch-show-markup-headers-hook))))) (define-button-type 'notmuch-show-part-button-type - 'action 'notmuch-show-part-button-action + 'action 'notmuch-show-part-button-default + 'keymap 'notmuch-show-part-button-map 'follow-link t 'face 'message-mml) +(defvar notmuch-show-part-button-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map button-map) + (define-key map "s" 'notmuch-show-part-button-save) + (define-key map "v" 'notmuch-show-part-button-view) + (define-key map "o" 'notmuch-show-part-button-interactively-view) + map) + "Submap for button commands") +(fset 'notmuch-show-part-button-map notmuch-show-part-button-map) + (defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment) (let ((button)) (setq button @@ -348,29 +369,58 @@ message at DEPTH in the current thread." " ]") :type 'notmuch-show-part-button-type :notmuch-part nth - :notmuch-filename name)) + :notmuch-filename name + :notmuch-content-type content-type)) (insert "\n") ;; return button button)) ;; Functions handling particular MIME parts. -(defun notmuch-show-save-part (message-id nth &optional filename) - (let ((process-crypto notmuch-show-process-crypto)) - (with-temp-buffer - (setq notmuch-show-process-crypto process-crypto) - ;; Always acquires the part via `notmuch part', even if it is - ;; available in the JSON output. - (insert (notmuch-show-get-bodypart-internal message-id nth)) - (let ((file (read-file-name - "Filename to save as: " - (or mailcap-download-directory "~/") - nil nil - filename))) - ;; Don't re-compress .gz & al. Arguably we should make - ;; `file-name-handler-alist' nil, but that would chop - ;; ange-ftp, which is reasonable to use here. - (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t))))) +(defmacro notmuch-with-temp-part-buffer (message-id nth &rest body) + (declare (indent 2)) + (let ((process-crypto (make-symbol "process-crypto"))) + `(let ((,process-crypto notmuch-show-process-crypto)) + (with-temp-buffer + (setq notmuch-show-process-crypto ,process-crypto) + ;; Always acquires the part via `notmuch part', even if it is + ;; available in the JSON output. + (insert (notmuch-show-get-bodypart-internal ,message-id ,nth)) + ,@body)))) + +(defun notmuch-show-save-part (message-id nth &optional filename content-type) + (notmuch-with-temp-part-buffer message-id nth + (let ((file (read-file-name + "Filename to save as: " + (or mailcap-download-directory "~/") + nil nil + filename))) + ;; Don't re-compress .gz & al. Arguably we should make + ;; `file-name-handler-alist' nil, but that would chop + ;; ange-ftp, which is reasonable to use here. + (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t)))) + +(defun notmuch-show-view-part (message-id nth &optional filename content-type ) + (notmuch-with-temp-part-buffer message-id nth + ;; set mm-inlined-types to nil to force an external viewer + (let ((handle (mm-make-handle (current-buffer) (list content-type))) + (mm-inlined-types nil)) + ;; We override mm-save-part as notmuch-show-save-part is better + ;; since it offers the filename. We need to lexically bind + ;; everything we need for notmuch-show-save-part to prevent + ;; potential dynamic shadowing. + (lexical-let ((message-id message-id) + (nth nth) + (filename filename) + (content-type content-type)) + (flet ((mm-save-part (&rest args) (notmuch-show-save-part + message-id nth filename content-type))) + (mm-display-part handle)))))) + +(defun notmuch-show-interactively-view-part (message-id nth &optional filename content-type) + (notmuch-with-temp-part-buffer message-id nth + (let ((handle (mm-make-handle (current-buffer) (list content-type)))) + (mm-interactively-view-part handle)))) (defun notmuch-show-mm-display-part-inline (msg part nth content-type) "Use the mm-decode/mm-view functions to display a part in the @@ -1555,12 +1605,30 @@ buffer." ;; Commands typically bound to buttons. -(defun notmuch-show-part-button-action (button) - (let ((nth (button-get button :notmuch-part))) - (if nth - (notmuch-show-save-part (notmuch-show-get-message-id) nth - (button-get button :notmuch-filename)) - (message "Not a valid part (is it a fake part?).")))) +(defun notmuch-show-part-button-default (&optional button) + (interactive) + (notmuch-show-part-button-internal button notmuch-show-part-button-default-action)) + +(defun notmuch-show-part-button-save (&optional button) + (interactive) + (notmuch-show-part-button-internal button #'notmuch-show-save-part)) + +(defun notmuch-show-part-button-view (&optional button) + (interactive) + (notmuch-show-part-button-internal button #'notmuch-show-view-part)) + +(defun notmuch-show-part-button-interactively-view (&optional button) + (interactive) + (notmuch-show-part-button-internal button #'notmuch-show-interactively-view-part)) + +(defun notmuch-show-part-button-internal (button handler) + (let ((button (or button (button-at (point))))) + (if button + (let ((nth (button-get button :notmuch-part))) + (if nth + (funcall handler (notmuch-show-get-message-id) nth + (button-get button :notmuch-filename) + (button-get button :notmuch-content-type))))))) ;; -- cgit v1.2.3 From 37dec7d7b37afd281f23c0ec7ed9111c24965126 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sat, 21 Jan 2012 16:44:28 +0200 Subject: emacs/*.el: changed one-char comment prefix ';' to two; ';;' In order for emacs (indent-region) to (re)indent emacs lisp properly there needs to be at least 2 comment characters (;;). --- emacs/notmuch-hello.el | 4 +- emacs/notmuch-lib.el | 16 +++---- emacs/notmuch-show.el | 16 +++---- emacs/notmuch.el | 120 ++++++++++++++++++++++++------------------------- 4 files changed, 78 insertions(+), 78 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 1a61768..63f2e07 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -402,8 +402,8 @@ Complete list of currently available key bindings: "Run notmuch and display saved searches, known tags, etc." (interactive) - ; Jump through a hoop to get this value from the deprecated variable - ; name (`notmuch-folders') or from the default value. + ;; Jump through a hoop to get this value from the deprecated variable + ;; name (`notmuch-folders') or from the default value. (unless notmuch-saved-searches (setq notmuch-saved-searches (notmuch-saved-searches))) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index e33e69a..241fe8c 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -148,14 +148,14 @@ the user hasn't set this variable with the old or new value." (setq list (cdr list))) (nreverse out))) -; This lets us avoid compiling these replacement functions when emacs -; is sufficiently new enough to supply them alone. We do the macro -; treatment rather than just wrapping our defun calls in a when form -; specifically so that the compiler never sees the code on new emacs, -; (since the code is triggering warnings that we don't know how to get -; rid of. -; -; A more clever macro here would accept a condition and a list of forms. +;; This lets us avoid compiling these replacement functions when emacs +;; is sufficiently new enough to supply them alone. We do the macro +;; treatment rather than just wrapping our defun calls in a when form +;; specifically so that the compiler never sees the code on new emacs, +;; (since the code is triggering warnings that we don't know how to get +;; rid of. +;; +;; A more clever macro here would accept a condition and a list of forms. (defmacro compile-on-emacs-prior-to-23 (form) "Conditionally evaluate form only on emacs < emacs-23." (list 'when (< emacs-major-version 23) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 3fb13ab..e6a5b31 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -137,14 +137,14 @@ indentation." "Use external viewers to view all attachments from the current message." (interactive) (with-current-notmuch-show-message - ; We override the mm-inline-media-tests to indicate which message - ; parts are already sufficiently handled by the original - ; presentation of the message in notmuch-show mode. These parts - ; will be inserted directly into the temporary buffer of - ; with-current-notmuch-show-message and silently discarded. - ; - ; Any MIME part not explicitly mentioned here will be handled by an - ; external viewer as configured in the various mailcap files. + ;; We override the mm-inline-media-tests to indicate which message + ;; parts are already sufficiently handled by the original + ;; presentation of the message in notmuch-show mode. These parts + ;; will be inserted directly into the temporary buffer of + ;; with-current-notmuch-show-message and silently discarded. + ;; + ;; Any MIME part not explicitly mentioned here will be handled by an + ;; external viewer as configured in the various mailcap files. (let ((mm-inline-media-tests '( ("text/.*" ignore identity) ("application/pgp-signature" ignore identity) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index da75faf..6b2c252 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -1,51 +1,51 @@ -; notmuch.el --- run notmuch within emacs -; -; Copyright © Carl Worth -; -; This file is part of Notmuch. -; -; Notmuch is free software: you can redistribute it and/or modify it -; under the terms of the GNU General Public License as published by -; the Free Software Foundation, either version 3 of the License, or -; (at your option) any later version. -; -; Notmuch is distributed in the hope that it will be useful, but -; WITHOUT ANY WARRANTY; without even the implied warranty of -; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -; General Public License for more details. -; -; You should have received a copy of the GNU General Public License -; along with Notmuch. If not, see . -; -; Authors: Carl Worth - -; This is an emacs-based interface to the notmuch mail system. -; -; You will first need to have the notmuch program installed and have a -; notmuch database built in order to use this. See -; http://notmuchmail.org for details. -; -; To install this software, copy it to a directory that is on the -; `load-path' variable within emacs (a good candidate is -; /usr/local/share/emacs/site-lisp). If you are viewing this from the -; notmuch source distribution then you can simply run: -; -; sudo make install-emacs -; -; to install it. -; -; Then, to actually run it, add: -; -; (require 'notmuch) -; -; to your ~/.emacs file, and then run "M-x notmuch" from within emacs, -; or run: -; -; emacs -f notmuch -; -; Have fun, and let us know if you have any comment, questions, or -; kudos: Notmuch list (subscription is not -; required, but is available from http://notmuchmail.org). +;; notmuch.el --- run notmuch within emacs +;; +;; Copyright © Carl Worth +;; +;; This file is part of Notmuch. +;; +;; Notmuch is free software: you can redistribute it and/or modify it +;; under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; Notmuch is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with Notmuch. If not, see . +;; +;; Authors: Carl Worth + +;; This is an emacs-based interface to the notmuch mail system. +;; +;; You will first need to have the notmuch program installed and have a +;; notmuch database built in order to use this. See +;; http://notmuchmail.org for details. +;; +;; To install this software, copy it to a directory that is on the +;; `load-path' variable within emacs (a good candidate is +;; /usr/local/share/emacs/site-lisp). If you are viewing this from the +;; notmuch source distribution then you can simply run: +;; +;; sudo make install-emacs +;; +;; to install it. +;; +;; Then, to actually run it, add: +;; +;; (require 'notmuch) +;; +;; to your ~/.emacs file, and then run "M-x notmuch" from within emacs, +;; or run: +;; +;; emacs -f notmuch +;; +;; Have fun, and let us know if you have any comment, questions, or +;; kudos: Notmuch list (subscription is not +;; required, but is available from http://notmuchmail.org). (eval-when-compile (require 'cl)) (require 'mm-view) @@ -139,10 +139,10 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-." "M-" (concat desc " ")))) -; I would think that emacs would have code handy for walking a keymap -; and generating strings for each key, and I would prefer to just call -; that. But I couldn't find any (could be all implemented in C I -; suppose), so I wrote my own here. +;; I would think that emacs would have code handy for walking a keymap +;; and generating strings for each key, and I would prefer to just call +;; that. But I couldn't find any (could be all implemented in C I +;; suppose), so I wrote my own here. (defun notmuch-substitute-one-command-key-with-prefix (prefix binding) "For a key binding, return a string showing a human-readable representation of the prefixed key as well as the first line of @@ -271,14 +271,14 @@ For a mouse binding, return nil." (defun notmuch-search-scroll-down () "Move backward through the search results by one window's worth." (interactive) - ; I don't know why scroll-down doesn't signal beginning-of-buffer - ; the way that scroll-up signals end-of-buffer, but c'est la vie. - ; - ; So instead of trapping a signal we instead check whether the - ; window begins on the first line of the buffer and if so, move - ; directly to that position. (We have to count lines since the - ; window-start position is not the same as point-min due to the - ; invisible thread-ID characters on the first line. + ;; I don't know why scroll-down doesn't signal beginning-of-buffer + ;; the way that scroll-up signals end-of-buffer, but c'est la vie. + ;; + ;; So instead of trapping a signal we instead check whether the + ;; window begins on the first line of the buffer and if so, move + ;; directly to that position. (We have to count lines since the + ;; window-start position is not the same as point-min due to the + ;; invisible thread-ID characters on the first line. (if (equal (count-lines (point-min) (window-start)) 0) (goto-char (point-min)) (scroll-down nil))) -- cgit v1.2.3 From 871fc32837d1e734895bef5f89040b5b874ae473 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 10 Jan 2012 08:07:07 -0400 Subject: uncrustify.cfg: initial support for notmuch coding style Uncrustify is a free (as in GPL2+) tool that indents and beautifies C/C++ code. It is similar to GNU indent in functionality although probably more configurable (in fairness, indent has better documentation). Uncrustify does not have the indent mis-feature of needing to have every typedef'ed type defined in the configuration (even standard types like size_t). This configuration starts with the linux-kernel style from the uncrustify config, disables aggressive re-indenting of structs, and fine tunes the handling 'else' and braces. In an ideal situation, running uncrustify on notmuch code would be NOP; currently this is not true for all files because 1) the configuration is not perfect 2) the coding style of notmuch is not completely consistent; in particular the treatment of braces after e.g. for (_) is not consistent. Some fine tuning by Tomi Olilla. --- devel/uncrustify.cfg | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 devel/uncrustify.cfg diff --git a/devel/uncrustify.cfg b/devel/uncrustify.cfg new file mode 100644 index 0000000..1dbc5e4 --- /dev/null +++ b/devel/uncrustify.cfg @@ -0,0 +1,105 @@ +# +# uncrustify config file for the linux kernel +# +# $Id: linux-indent.cfg 488 2006-09-09 12:44:38Z bengardner $ +# Taken from the uncrustify distribution under license (GPL2+) +# +# sample usage: +# uncrustify --replace -c uncrustify.cfg foo.c +# +# + +indent_with_tabs = 2 # 1=indent to level only, 2=indent with tabs +align_with_tabs = TRUE # use tabs to align +align_on_tabstop = TRUE # align on tabstops +input_tab_size = 8 # original tab size +output_tab_size = 8 # new tab size +indent_columns = 4 + +indent_label = 2 # pos: absolute col, neg: relative column + + +# +# inter-symbol newlines +# + +nl_enum_brace = remove # "enum {" vs "enum \n {" +nl_union_brace = remove # "union {" vs "union \n {" +nl_struct_brace = remove # "struct {" vs "struct \n {" +nl_do_brace = remove # "do {" vs "do \n {" +nl_if_brace = remove # "if () {" vs "if () \n {" +nl_for_brace = remove # "for () {" vs "for () \n {" +nl_else_brace = remove # "else {" vs "else \n {" +nl_while_brace = remove # "while () {" vs "while () \n {" +nl_switch_brace = remove # "switch () {" vs "switch () \n {" +nl_brace_while = remove # "} while" vs "} \n while" - cuddle while +nl_brace_else = remove # "} else" vs "} \n else" - cuddle else +nl_func_var_def_blk = 1 +nl_fcall_brace = remove # "list_for_each() {" vs "list_for_each()\n{" +nl_fdef_brace = force # "int foo() {" vs "int foo()\n{" +# nl_after_return = TRUE; +# nl_before_case = 1 + +# Add or remove newline between return type and function name in definition +nl_func_type_name = force +nl_enum_leave_one_liners = True +nl_enum_brace = Remove +nl_after_struct = 0 +# +# Source code modifications +# + +# mod_paren_on_return = remove # "return 1;" vs "return (1);" +# mod_full_brace_if = remove # "if (a) a--;" vs "if (a) { a--; }" +# mod_full_brace_for = remove # "for () a--;" vs "for () { a--; }" +# mod_full_brace_do = remove # "do a--; while ();" vs "do { a--; } while ();" +# mod_full_brace_while = remove # "while (a) a--;" vs "while (a) { a--; }" + + +# +# inter-character spacing options +# + +sp_before_ptr_star = force +sp_between_ptr_star = remove +sp_after_ptr_star = remove + +# sp _return_paren = force # "return (1);" vs "return(1);" +sp_sizeof_paren = force # "sizeof (int)" vs "sizeof(int)" +sp_before_sparen = force # "if (" vs "if(" +sp_after_sparen = force # "if () {" vs "if (){" +sp_sparen_brace = force +sp_after_cast = force # "(int) a" vs "(int)a" +sp_inside_braces = add # "{ 1 }" vs "{1}" +sp_inside_braces_struct = add # "{ 1 }" vs "{1}" +sp_inside_braces_enum = add # "{ 1 }" vs "{1}" +sp_assign = force +sp_arith = force +sp_bool = add +sp_compare = add +sp_assign = add +sp_after_comma = add +sp_func_def_paren = force # "int foo (){" vs "int foo(){" +sp_func_call_paren = force # "foo (" vs "foo(" +sp_func_proto_paren = force # "int foo ();" vs "int foo();" +sp_brace_else = force # "} else" vs "}else" +sp_else_brace = force # "else {" vs "else{" +# +# Aligning stuff +# + +align_enum_equ_span = 4 # '=' in enum definition +# align_nl_cont = TRUE +# align_var_def_span = 2 +# align_var_def_inline = TRUE +# align_var_def_star = FALSE +# align_var_def_colon = TRUE +# align_assign_span = 1 +align_struct_init_span = 0 # align stuff in a structure init '= { }' +align_right_cmt_span = 8 # align comments span this much in func +# align_pp_define_span = 8; +# align_pp_define_gap = 4; + +# cmt_star_cont = FALSE + +# indent_brace = 0 -- cgit v1.2.3 From 8015cbff263606f009b5750d23b28ee332c25db8 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sun, 22 Jan 2012 06:14:57 +0100 Subject: python: fix error handling Before 3434d1940 the return values of libnotmuch functions were declared as c_void_p and the code checking for errors compared the returned value to None, which is the ctypes equivalent of a NULL pointer. But said commit wrapped all the data types in python classes and the semantic changed in a subtle way. If a function returns NULL, the wrapped python value is falsish, but no longer equal to None. --- bindings/python/notmuch/database.py | 16 ++++++++-------- bindings/python/notmuch/filename.py | 2 +- bindings/python/notmuch/message.py | 6 +++--- bindings/python/notmuch/tag.py | 2 +- bindings/python/notmuch/thread.py | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 24da8e9..6238b28 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -168,7 +168,7 @@ class Database(object): res = Database._create(_str(path), Database.MODE.READ_WRITE) - if res is None: + if not res: raise NotmuchError( message="Could not create the specified database") self._db = res @@ -188,7 +188,7 @@ class Database(object): """ res = Database._open(_str(path), mode) - if res is None: + if not res: raise NotmuchError(message="Could not open the specified database") self._db = res @@ -651,7 +651,7 @@ class Query(object): self._db = db # create query, return None if too little mem available query_p = Query._create(db.db_p, _str(querystr)) - if query_p is None: + if not query_p: raise NullPointerError self._query = query_p @@ -685,7 +685,7 @@ class Query(object): self._assert_query_is_initialized() threads_p = Query._search_threads(self._query) - if threads_p is None: + if not threads_p: raise NullPointerError return Threads(threads_p, self) @@ -699,7 +699,7 @@ class Query(object): self._assert_query_is_initialized() msgs_p = Query._search_messages(self._query) - if msgs_p is None: + if not msgs_p: raise NullPointerError return Messages(msgs_p, self) @@ -765,7 +765,7 @@ class Directory(object): def _assert_dir_is_initialized(self): """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None""" - if self._dir_p is None: + if not self._dir_p: raise NotmuchError(STATUS.NOT_INITIALIZED) def __init__(self, path, dir_p, parent): @@ -926,7 +926,7 @@ class Filenames(object): _move_to_next.restype = None def __next__(self): - if self._files_p is None: + if not self._files_p: raise NotmuchError(STATUS.NOT_INITIALIZED) if not self._valid(self._files_p): @@ -953,7 +953,7 @@ class Filenames(object): # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) for file in files: print file """ - if self._files_p is None: + if not self._files_p: raise NotmuchError(STATUS.NOT_INITIALIZED) i = 0 diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index 51dae20..3f54104 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -69,7 +69,7 @@ class Filenames(Python3StringMixIn): reference to it, so we can automatically delete the db object once all derived objects are dead. """ - if files_p is None: + if not files_p: raise NotmuchError(STATUS.NULL_POINTER) self._files = files_p diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index d40a575..883ed23 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -117,7 +117,7 @@ class Messages(object): :TODO: Make the iterator work more than once and cache the tags in the Python object.(?) """ - if msgs_p is None: + if not msgs_p: raise NotmuchError(STATUS.NULL_POINTER) self._msgs = msgs_p @@ -349,7 +349,7 @@ class Message(Python3StringMixIn): automatically delete the parent object once all derived objects are dead. """ - if msg_p is None: + if not msg_p: raise NotmuchError(STATUS.NULL_POINTER) self._msg = msg_p #keep reference to parent, so we keep it alive @@ -407,7 +407,7 @@ class Message(Python3StringMixIn): msgs_p = Message._get_replies(self._msg) - if msgs_p is None: + if not msgs_p: return EmptyMessagesResult(self) return Messages(msgs_p, self) diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index ceb7244..71d81dd 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -70,7 +70,7 @@ class Tags(Python3StringMixIn): :TODO: Make the iterator optionally work more than once by cache the tags in the Python object(?) """ - if tags_p is None: + if not tags_p: raise NotmuchError(STATUS.NULL_POINTER) self._tags = tags_p diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index e81ff1b..104710c 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -97,7 +97,7 @@ class Threads(Python3StringMixIn): :TODO: Make the iterator work more than once and cache the tags in the Python object.(?) """ - if threads_p is None: + if not threads_p: raise NotmuchError(STATUS.NULL_POINTER) self._threads = threads_p @@ -228,7 +228,7 @@ class Thread(object): automatically delete the parent object once all derived objects are dead. """ - if thread_p is None: + if not thread_p: raise NotmuchError(STATUS.NULL_POINTER) self._thread = thread_p #keep reference to parent, so we keep it alive @@ -289,7 +289,7 @@ class Thread(object): msgs_p = Thread._get_toplevel_messages(self._thread) - if msgs_p is None: + if not msgs_p: raise NotmuchError(STATUS.NULL_POINTER) return Messages(msgs_p, self) -- cgit v1.2.3 From d449c60a73cae2fdd88aa7c255e49d67c5a907ff Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Sun, 22 Jan 2012 01:29:41 +0100 Subject: Fix NEWS about gmime 2.6 Previous version had a typo ("they may be" instead of "there may be") and was lacking a proper description of the gmime bug. --- NEWS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index e78472c..2c2d9e9 100644 --- a/NEWS +++ b/NEWS @@ -42,8 +42,8 @@ Build fixes Compatibility with GMime 2.6 It is now possible to build notmuch against both GMime 2.4 and 2.6. - However they may be some issues in PGP signature verification - because of a bug in current versions of GMime 2.6. + However, a bug in current GMime 2.6 causes notmuch not to report + signatures where the signer key is unavailable (GNOME bug 668085). Notmuch 0.11 (2012-01-13) ========================= -- cgit v1.2.3 From 3f42e87030cf9a30f65ae082af2a53280f6c9100 Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Sun, 22 Jan 2012 01:20:57 +0100 Subject: show: don't use hex literals in JSON output JSON does not support hex literals (0x..) so numbers must be formatted as %d instead of %x. Currently, the possible values for the gmime error code are 1 (expired signature), 2 (no public key), 4 (expired key) and 8 (revoked key). The other possible value is 16 (unsupported algorithm) but obviously it is much more rare. If this happens, the current code will add '"errors": 10'. This is valid JSON (it looks like a decimal number) but it is incorrect (should be 16, not 10). Since this is just an issue in the JSON encoder, no changes are needed on the Emacs side (or in other UIs using the JSON output). --- notmuch-show.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notmuch-show.c b/notmuch-show.c index 43ee211..7b40568 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -728,7 +728,7 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity) printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid)); } if (signer->errors != GMIME_SIGNER_ERROR_NONE) { - printf (", \"errors\": %x", signer->errors); + printf (", \"errors\": %d", signer->errors); } printf ("}"); -- cgit v1.2.3 From ce3513261d447c96f59a5d5f4a18ec55beed92f4 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Sun, 22 Jan 2012 10:35:47 +0200 Subject: .dir-locals.el: changed one-char comment prefix '; ' to two; '; ; ' Like in emacs/*.el two comment chars (;;) is required so that (indent-region) doesn't break indentation. --- .dir-locals.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index 044c214..fc75ae6 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,6 +1,6 @@ -; emacs local configuration settings for notmuch source -; surmised by dkg on 2010-11-23 13:43:18-0500 -; amended by amdragon on 2011-06-06 +;; emacs local configuration settings for notmuch source +;; surmised by dkg on 2010-11-23 13:43:18-0500 +;; amended by amdragon on 2011-06-06 ((c-mode (indent-tabs-mode . t) -- cgit v1.2.3 From da9f39216555934327a91ebc6b3b726b0a989dcf Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 11 Jan 2012 18:53:59 +0200 Subject: test: whitespace-cleanup for most test/* files Used emacs (whitespace-cleanup) function to "cleanup blank problems" in test files where that could be done without breaking tests; test/emacs was partially, and test/multipart was fully reverted. --- test/emacs | 50 ++++++++-------- test/encoding | 2 +- test/from-guessing | 126 +++++++++++++++++++-------------------- test/reply | 58 +++++++++--------- test/search-position-overlap-bug | 4 +- test/symbol-hiding | 2 +- test/test-lib.sh | 10 ++-- test/thread-naming | 30 +++++----- 8 files changed, 141 insertions(+), 141 deletions(-) diff --git a/test/emacs b/test/emacs index ac47b16..f150d95 100755 --- a/test/emacs +++ b/test/emacs @@ -225,7 +225,7 @@ test_expect_equal_file OUTPUT EXPECTED mkdir -p mail/sent-list-catch-all/cur mkdir -p mail/sent-list-catch-all/new mkdir -p mail/sent-list-catch-all/tmp - + test_begin_subtest "notmuch-fcc-dirs set to a list (catch-all)" test_emacs "(let ((notmuch-fcc-dirs '((\"example.com\" . \"failure\") @@ -373,22 +373,22 @@ add_message '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \ '[body]="Unable to stash body. Where did you get it in the first place?!?"' notmuch tag +stashtest id:${gen_msg_id} test_emacs '(notmuch-show "id:\"bought\"") - (notmuch-show-stash-date) - (notmuch-show-stash-from) - (notmuch-show-stash-to) - (notmuch-show-stash-cc) - (notmuch-show-stash-subject) - (notmuch-show-stash-message-id) - (notmuch-show-stash-message-id-stripped) - (notmuch-show-stash-tags) - (notmuch-show-stash-filename) - (switch-to-buffer - (generate-new-buffer "*test-stashing*")) - (dotimes (i 9) - (yank) - (insert "\n") - (rotate-yank-pointer 1)) - (reverse-region (point-min) (point-max)) + (notmuch-show-stash-date) + (notmuch-show-stash-from) + (notmuch-show-stash-to) + (notmuch-show-stash-cc) + (notmuch-show-stash-subject) + (notmuch-show-stash-message-id) + (notmuch-show-stash-message-id-stripped) + (notmuch-show-stash-tags) + (notmuch-show-stash-filename) + (switch-to-buffer + (generate-new-buffer "*test-stashing*")) + (dotimes (i 9) + (yank) + (insert "\n") + (rotate-yank-pointer 1)) + (reverse-region (point-min) (point-max)) (test-output)' cat <EXPECTED Sat, 01 Jan 2000 12:00:00 -0000 @@ -405,11 +405,11 @@ test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Stashing in notmuch-search" test_emacs '(notmuch-search "id:\"bought\"") - (notmuch-test-wait) - (notmuch-search-stash-thread-id) - (switch-to-buffer - (generate-new-buffer "*test-stashing*")) - (yank) + (notmuch-test-wait) + (notmuch-search-stash-thread-id) + (switch-to-buffer + (generate-new-buffer "*test-stashing*")) + (yank) (test-output)' sed -i -e 's/^thread:.*$/thread:XXX/' OUTPUT test_expect_equal "$(cat OUTPUT)" "thread:XXX" @@ -438,9 +438,9 @@ test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Refresh modified show buffer" test_subtest_known_broken test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com") - (notmuch-show-toggle-message) - (notmuch-show-next-message) - (notmuch-show-toggle-message) + (notmuch-show-toggle-message) + (notmuch-show-next-message) + (notmuch-show-toggle-message) (test-visible-output "EXPECTED") (notmuch-show-refresh-view) (test-visible-output)' diff --git a/test/encoding b/test/encoding index e875c8b..33259c1 100755 --- a/test/encoding +++ b/test/encoding @@ -4,7 +4,7 @@ test_description="encoding issues" test_begin_subtest "Message with text of unknown charset" add_message '[content-type]="text/plain; charset=unknown-8bit"' \ - "[body]=irrelevant" + "[body]=irrelevant" output=$(notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize) test_expect_equal "$output" " message{ id:msg-001@notmuch-test-suite depth:0 match:1 filename:/XXX/mail/msg-001 header{ diff --git a/test/from-guessing b/test/from-guessing index 8b69cf6..6dfaa40 100755 --- a/test/from-guessing +++ b/test/from-guessing @@ -4,10 +4,10 @@ test_description="From line heuristics (with multiple configured addresses)" test_begin_subtest "Magic from guessing (nothing to go on)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -21,11 +21,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (Envelope-to:)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[header]="Envelope-To: test_suite_other@notmuchmail.org"' \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[header]="Envelope-To: test_suite_other@notmuchmail.org"' \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -39,11 +39,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (X-Original-To:)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[header]="X-Original-To: test_suite_other@notmuchmail.org"' \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[header]="X-Original-To: test_suite_other@notmuchmail.org"' \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -57,13 +57,13 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (Received: .. for ..)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) - by mail.notmuchmail.org (some MTA) with ESMTP id 12345678 - for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) + by mail.notmuchmail.org (some MTA) with ESMTP id 12345678 + for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -77,13 +77,13 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (Received: domain)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) - by mail.otherdomain.org (some MTA) with ESMTP id 12345678 - Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) + by mail.otherdomain.org (some MTA) with ESMTP id 12345678 + Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -97,15 +97,15 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (multiple Received: headers)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - "[header]=\"Received: from extraneous.example.com (extraneous.example.com [1.1.1.1]) + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + "[header]=\"Received: from extraneous.example.com (extraneous.example.com [1.1.1.1]) Received: from mail.example.com (mail.example.com [1.1.1.1]) - by mail.otherdomain.org (some MTA) with ESMTP id 12345678 - for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT) + by mail.otherdomain.org (some MTA) with ESMTP id 12345678 + for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT) Received: from extraneous.example.com (extraneous.example.com [1.1.1.1])\"" \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output="$(notmuch reply id:${gen_msg_id})" test_expect_equal "$output" "From: Notmuch Test Suite @@ -123,10 +123,10 @@ test_expect_equal '' '' test_begin_subtest "Magic from guessing (nothing to go on)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -140,11 +140,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (Envelope-to:)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[header]="Envelope-To: test_suite_other@notmuchmail.org"' \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[header]="Envelope-To: test_suite_other@notmuchmail.org"' \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -158,11 +158,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (X-Original-To:)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[header]="X-Original-To: test_suite_other@notmuchmail.org"' \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[header]="X-Original-To: test_suite_other@notmuchmail.org"' \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -176,13 +176,13 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (Received: .. for ..)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) - by mail.notmuchmail.org (some MTA) with ESMTP id 12345678 - for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) + by mail.notmuchmail.org (some MTA) with ESMTP id 12345678 + for ; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -196,13 +196,13 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Magic from guessing (Received: domain)" add_message '[from]="Sender "' \ - [to]=mailinglist@notmuchmail.org \ - [subject]=notmuch-reply-test \ - "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) - by mail.otherdomain.org (some MTA) with ESMTP id 12345678 - Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="from guessing test"' + [to]=mailinglist@notmuchmail.org \ + [subject]=notmuch-reply-test \ + "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1]) + by mail.otherdomain.org (some MTA) with ESMTP id 12345678 + Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="from guessing test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite diff --git a/test/reply b/test/reply index e4e16eb..00f4bea 100755 --- a/test/reply +++ b/test/reply @@ -4,10 +4,10 @@ test_description="\"notmuch reply\" in several variations" test_begin_subtest "Basic reply" add_message '[from]="Sender "' \ - [to]=test_suite@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="basic reply test"' + [to]=test_suite@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="basic reply test"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -21,10 +21,10 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Multiple recipients" add_message '[from]="Sender "' \ - '[to]="test_suite@notmuchmail.org, Someone Else "' \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="Multiple recipients"' + '[to]="test_suite@notmuchmail.org, Someone Else "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="Multiple recipients"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -38,11 +38,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Reply with CC" add_message '[from]="Sender "' \ - [to]=test_suite@notmuchmail.org \ - '[cc]="Other Parties "' \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="reply with CC"' + [to]=test_suite@notmuchmail.org \ + '[cc]="Other Parties "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="reply with CC"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -57,10 +57,10 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Reply from alternate address" add_message '[from]="Sender "' \ - [to]=test_suite_other@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="reply from alternate address"' + [to]=test_suite_other@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="reply from alternate address"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -92,11 +92,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Support for Reply-To" add_message '[from]="Sender "' \ - [to]=test_suite@notmuchmail.org \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="support for reply-to"' \ - '[reply-to]="Sender "' + [to]=test_suite@notmuchmail.org \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="support for reply-to"' \ + '[reply-to]="Sender "' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -110,11 +110,11 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Un-munging Reply-To" add_message '[from]="Sender "' \ - '[to]="Some List "' \ - [subject]=notmuch-reply-test \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="Un-munging Reply-To"' \ - '[reply-to]="Evil Munging List "' + '[to]="Some List "' \ + [subject]=notmuch-reply-test \ + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="Un-munging Reply-To"' \ + '[reply-to]="Evil Munging List "' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite @@ -128,8 +128,8 @@ On Tue, 05 Jan 2010 15:43:56 -0000, Sender wrote: test_begin_subtest "Message with header of exactly 200 bytes" add_message '[subject]="This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces"' \ - '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ - '[body]="200-byte header"' + '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \ + '[body]="200-byte header"' output=$(notmuch reply id:${gen_msg_id}) test_expect_equal "$output" "From: Notmuch Test Suite Subject: Re: This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces diff --git a/test/search-position-overlap-bug b/test/search-position-overlap-bug index 414b8d5..5da6ad6 100755 --- a/test/search-position-overlap-bug +++ b/test/search-position-overlap-bug @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Test to demonstrate a position overlap bug. -# +# # At one point, notmuch would index terms incorrectly in the case of # calling index_terms multiple times for a single field. The term # generator was being reset to position 0 each time. This means that @@ -12,7 +12,7 @@ # one could get a bogus match by searching for: # # To: a@y.c -# +# # Thanks to Mark Anderson for reporting the bug, (and providing a nice, # minimal test case that inspired what is used here), in # id:3wd4o8wa7fx.fsf@testarossa.amd.com diff --git a/test/symbol-hiding b/test/symbol-hiding index 7fa7b2a..636ec91 100755 --- a/test/symbol-hiding +++ b/test/symbol-hiding @@ -23,7 +23,7 @@ mkdir -p fakedb/.notmuch test_expect_success 'running test' run_test test_begin_subtest 'checking output' -test_expect_equal "$result" "$output" +test_expect_equal "$result" "$output" test_begin_subtest 'comparing existing to exported symbols' objdump -t $TEST_DIRECTORY/../lib/*.o | awk '$4 == ".text" && $6 ~ "^notmuch" {print $6}' | sort | uniq > ACTUAL diff --git a/test/test-lib.sh b/test/test-lib.sh index d1fbc05..0da60fb 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -140,7 +140,7 @@ if test -n "$color"; then esac shift printf " " - printf "$@" + printf "$@" tput sgr0 print_subtest ) @@ -150,7 +150,7 @@ else test -z "$1" && test -n "$quiet" && return shift printf " " - printf "$@" + printf "$@" print_subtest } fi @@ -249,7 +249,7 @@ remove_cr () { # Store the message in file 'name'. The default is to store it # in 'msg-', where is three-digit number of the # message. -# +# # [body]=text # # Text to use as the body of the email message @@ -869,7 +869,7 @@ test_done () { [ -n "$EMACS_SERVER" ] && test_emacs '(kill-emacs)' if [ "$test_failure" = "0" ]; then - if [ "$test_broken" = "0" ]; then + if [ "$test_broken" = "0" ]; then rm -rf "$remove_tmp" fi exit 0 @@ -881,7 +881,7 @@ test_done () { emacs_generate_script () { # Construct a little test script here for the benefit of the user, # (who can easily run "run_emacs" to get the same emacs environment - # for investigating any failures). + # for investigating any failures). cat <"$TMP_DIRECTORY/run_emacs" #!/bin/sh export PATH=$PATH diff --git a/test/thread-naming b/test/thread-naming index 41b97d9..2ce9216 100755 --- a/test/thread-naming +++ b/test/thread-naming @@ -4,18 +4,18 @@ test_description="naming of threads with changing subject" test_begin_subtest "Initial thread name (oldest-first search)" add_message '[subject]="thread-naming: Initial thread subject"' \ - '[date]="Fri, 05 Jan 2001 15:43:56 -0000"' + '[date]="Fri, 05 Jan 2001 15:43:56 -0000"' first=${gen_msg_cnt} parent=${gen_msg_id} add_message '[subject]="thread-naming: Older changed subject"' \ - '[date]="Sat, 06 Jan 2001 15:43:56 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Sat, 06 Jan 2001 15:43:56 -0000"' \ + "[in-reply-to]=\<$parent\>" add_message '[subject]="thread-naming: Newer changed subject"' \ - '[date]="Sun, 07 Jan 2001 15:43:56 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Sun, 07 Jan 2001 15:43:56 -0000"' \ + "[in-reply-to]=\<$parent\>" add_message '[subject]="thread-naming: Final thread subject"' \ - '[date]="Mon, 08 Jan 2001 15:43:56 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Mon, 08 Jan 2001 15:43:56 -0000"' \ + "[in-reply-to]=\<$parent\>" final=${gen_msg_id} output=$(notmuch search --sort=oldest-first thread-naming and tag:inbox | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [4/4] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)" @@ -37,29 +37,29 @@ test_expect_equal "$output" "thread:XXX 2001-01-07 [2/4] Notmuch Test Suite; t test_begin_subtest "Ignore added reply prefix (Re:)" add_message '[subject]="Re: thread-naming: Initial thread subject"' \ - '[date]="Tue, 09 Jan 2001 15:43:45 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Tue, 09 Jan 2001 15:43:45 -0000"' \ + "[in-reply-to]=\<$parent\>" output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-09 [3/5] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)" test_begin_subtest "Ignore added reply prefix (Aw:)" add_message '[subject]="Aw: thread-naming: Initial thread subject"' \ - '[date]="Wed, 10 Jan 2001 15:43:45 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Wed, 10 Jan 2001 15:43:45 -0000"' \ + "[in-reply-to]=\<$parent\>" output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-10 [4/6] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)" test_begin_subtest "Ignore added reply prefix (Vs:)" add_message '[subject]="Vs: thread-naming: Initial thread subject"' \ - '[date]="Thu, 11 Jan 2001 15:43:45 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Thu, 11 Jan 2001 15:43:45 -0000"' \ + "[in-reply-to]=\<$parent\>" output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-11 [5/7] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)" test_begin_subtest "Ignore added reply prefix (Sv:)" add_message '[subject]="Sv: thread-naming: Initial thread subject"' \ - '[date]="Fri, 12 Jan 2001 15:43:45 -0000"' \ - "[in-reply-to]=\<$parent\>" + '[date]="Fri, 12 Jan 2001 15:43:45 -0000"' \ + "[in-reply-to]=\<$parent\>" output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-12 [6/8] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)" -- cgit v1.2.3 From a04642043fa8f071e0dbc73a204fcc91dc3c8401 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Fri, 13 Jan 2012 10:17:28 +0200 Subject: test: make (kill-emacs) from emacsclient work with emacs 23.(1|2) emacsclient --eval '(kill-emacs)' makes emacs versions 23.1 and 23.2 ask user input from running emacs. Redefining yes-or-no-p function when kill-emacs is executed for these emacs versions in test-lib.el avoids this test problem. --- test/test-lib.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test-lib.el b/test/test-lib.el index 3b817c3..59c5868 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -26,6 +26,13 @@ ;; `read' call. (setq read-file-name-function (lambda (&rest _) (read))) +;; Work around a bug in emacs 23.1 and emacs 23.2 which prevents +;; noninteractive (kill-emacs) from emacsclient. +(if (and (= emacs-major-version 23) (< emacs-minor-version 3)) + (defadvice kill-emacs (before disable-yes-or-no-p activate) + "Disable yes-or-no-p before executing kill-emacs" + (defun yes-or-no-p (prompt) t))) + (defun notmuch-test-wait () "Wait for process completion." (while (get-buffer-process (current-buffer)) -- cgit v1.2.3 From 80771e3c76573613b18b700983e0d29fea5630a3 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 23 Jan 2012 05:26:10 +0100 Subject: test/emacs-large-search-buffer: correct typo (EXPEXTED -> EXPECTED) introduced in commit 3b24b396 --- test/emacs-large-search-buffer | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/emacs-large-search-buffer b/test/emacs-large-search-buffer index 6095e9d..4351e33 100755 --- a/test/emacs-large-search-buffer +++ b/test/emacs-large-search-buffer @@ -19,25 +19,25 @@ done notmuch new > /dev/null test_begin_subtest "Ensure that emacs doesn't drop results" -notmuch search '*' > EXPEXTED -sed -i -e 's/^thread:[0-9a-f]* //' -e 's/;//' -e 's/xx*/[BLOB]/' EXPEXTED -echo 'End of search results.' >> EXPEXTED +notmuch search '*' > EXPECTED +sed -i -e 's/^thread:[0-9a-f]* //' -e 's/;//' -e 's/xx*/[BLOB]/' EXPECTED +echo 'End of search results.' >> EXPECTED test_emacs '(notmuch-search "*") (notmuch-test-wait) (test-output)' sed -i -e s', *, ,g' -e 's/xxx*/[BLOB]/g' OUTPUT -test_expect_equal_file OUTPUT EXPEXTED +test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Ensure that emacs doesn't drop error messages" test_emacs '(notmuch-search "--this-option-does-not-exist") (notmuch-test-wait) (test-output)' -cat <EXPEXTED +cat <EXPECTED Error: Unexpected output from notmuch search: Unrecognized option: --this-option-does-not-exist End of search results. (process returned 1) EOF -test_expect_equal_file OUTPUT EXPEXTED +test_expect_equal_file OUTPUT EXPECTED test_done -- cgit v1.2.3 From 0bd09f844677ba361318d129bd01e21b0f7c1bd9 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 22 Jan 2012 21:31:12 -0500 Subject: show: Use consistent header ordering in the text format Previously, top-level message headers were printed as Subject, From, To, Date, while embedded message headers were printed From, To, Subject, Date. This makes both cases use the former order and updates the tests accordingly. Strangely, the raw format also uses this function, so this also fixes the two raw format tests affected by this change. --- notmuch-show.c | 2 +- test/multipart | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 7b40568..682aa71 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -364,6 +364,7 @@ format_headers_message_part_text (GMimeMessage *message) InternetAddressList *recipients; const char *recipients_string; + printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("From: %s\n", g_mime_message_get_sender (message)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); recipients_string = internet_address_list_to_string (recipients, 0); @@ -375,7 +376,6 @@ format_headers_message_part_text (GMimeMessage *message) if (recipients_string) printf ("Cc: %s\n", recipients_string); - printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); } diff --git a/test/multipart b/test/multipart index f83526b..2dd73f5 100755 --- a/test/multipart +++ b/test/multipart @@ -121,9 +121,9 @@ Date: Fri, 05 Jan 2001 15:43:57 +0000 part{ ID: 2, Content-type: multipart/mixed part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -162,9 +162,9 @@ cat <EXPECTED part{ ID: 2, Content-type: multipart/mixed part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -200,9 +200,9 @@ cat <EXPECTED part{ ID: 2, Content-type: multipart/mixed part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -233,9 +233,9 @@ notmuch show --format=text --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OU cat <EXPECTED part{ ID: 3, Content-type: message/rfc822 header{ +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 header} body{ @@ -452,9 +452,9 @@ notmuch show --format=raw --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUT # output should *not* include newline echo >>OUTPUT cat <EXPECTED +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000

This is an embedded message, with a multipart/alternative part.

@@ -476,9 +476,9 @@ test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "--format=raw --part=2, multipart/mixed" notmuch show --format=raw --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED +Subject: html message From: Carl Worth To: cworth@cworth.org -Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000

This is an embedded message, with a multipart/alternative part.

-- cgit v1.2.3 From 221c7e0b38177f5f1dbf0561580c15e8aaa49004 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sun, 22 Jan 2012 14:09:35 +0100 Subject: python: fix error handling Before 3434d1940 the return values of libnotmuch functions were declared as c_void_p and the code checking for errors compared the returned value to None, which is the ctypes equivalent of a NULL pointer. But said commit wrapped all the data types in python classes and the semantic changed in a subtle way. If a function returns NULL, the wrapped python value is falsish, but no longer equal to None. Backported from master to 0.11. --- bindings/python/notmuch/database.py | 16 ++++++++-------- bindings/python/notmuch/filename.py | 2 +- bindings/python/notmuch/message.py | 6 +++--- bindings/python/notmuch/tag.py | 2 +- bindings/python/notmuch/thread.py | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 7923f76..0074ba3 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -168,7 +168,7 @@ class Database(object): res = Database._create(_str(path), Database.MODE.READ_WRITE) - if res is None: + if not res: raise NotmuchError( message="Could not create the specified database") self._db = res @@ -188,7 +188,7 @@ class Database(object): """ res = Database._open(_str(path), mode) - if res is None: + if not res: raise NotmuchError(message="Could not open the specified database") self._db = res @@ -645,7 +645,7 @@ class Query(object): self._db = db # create query, return None if too little mem available query_p = Query._create(db.db_p, _str(querystr)) - if query_p is None: + if not query_p: raise NullPointerError self._query = query_p @@ -679,7 +679,7 @@ class Query(object): self._assert_query_is_initialized() threads_p = Query._search_threads(self._query) - if threads_p is None: + if not threads_p: raise NullPointerError return Threads(threads_p, self) @@ -693,7 +693,7 @@ class Query(object): self._assert_query_is_initialized() msgs_p = Query._search_messages(self._query) - if msgs_p is None: + if not msgs_p: raise NullPointerError return Messages(msgs_p, self) @@ -759,7 +759,7 @@ class Directory(object): def _assert_dir_is_initialized(self): """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None""" - if self._dir_p is None: + if not self._dir_p: raise NotmuchError(STATUS.NOT_INITIALIZED) def __init__(self, path, dir_p, parent): @@ -920,7 +920,7 @@ class Filenames(object): _move_to_next.restype = None def next(self): - if self._files_p is None: + if not self._files_p: raise NotmuchError(STATUS.NOT_INITIALIZED) if not self._valid(self._files_p): @@ -946,7 +946,7 @@ class Filenames(object): # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) for file in files: print file """ - if self._files_p is None: + if not self._files_p: raise NotmuchError(STATUS.NOT_INITIALIZED) i = 0 diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index a7cd7e6..f7313ec 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -69,7 +69,7 @@ class Filenames(object): reference to it, so we can automatically delete the db object once all derived objects are dead. """ - if files_p is None: + if not files_p: raise NotmuchError(STATUS.NULL_POINTER) self._files = files_p diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index ce8e718..5540df3 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -116,7 +116,7 @@ class Messages(object): :TODO: Make the iterator work more than once and cache the tags in the Python object.(?) """ - if msgs_p is None: + if not msgs_p: raise NotmuchError(STATUS.NULL_POINTER) self._msgs = msgs_p @@ -321,7 +321,7 @@ class Message(object): automatically delete the parent object once all derived objects are dead. """ - if msg_p is None: + if not msg_p: raise NotmuchError(STATUS.NULL_POINTER) self._msg = msg_p #keep reference to parent, so we keep it alive @@ -380,7 +380,7 @@ class Message(object): msgs_p = Message._get_replies(self._msg) - if msgs_p is None: + if not msgs_p: return None return Messages(msgs_p, self) diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index 2fb7d32..4881db9 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -70,7 +70,7 @@ class Tags(object): :TODO: Make the iterator optionally work more than once by cache the tags in the Python object(?) """ - if tags_p is None: + if not tags_p: raise NotmuchError(STATUS.NULL_POINTER) self._tags = tags_p diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index 5058846..594fa52 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -97,7 +97,7 @@ class Threads(object): :TODO: Make the iterator work more than once and cache the tags in the Python object.(?) """ - if threads_p is None: + if not threads_p: raise NotmuchError(STATUS.NULL_POINTER) self._threads = threads_p @@ -227,7 +227,7 @@ class Thread(object): automatically delete the parent object once all derived objects are dead. """ - if thread_p is None: + if not thread_p: raise NotmuchError(STATUS.NULL_POINTER) self._thread = thread_p #keep reference to parent, so we keep it alive @@ -288,7 +288,7 @@ class Thread(object): msgs_p = Thread._get_toplevel_messages(self._thread) - if msgs_p is None: + if not msgs_p: raise NotmuchError(STATUS.NULL_POINTER) return Messages(msgs_p, self) -- cgit v1.2.3 From c8410bce8ba4fb40672f997e66ac4f94072b6349 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 23 Jan 2012 13:15:03 +0100 Subject: Add a NEWS section for 0.11.1 and document the python error handling bugfix --- NEWS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NEWS b/NEWS index bf21e64..3d2c2a8 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,17 @@ +Notmuch 0.11.1 (2012-mm-dd) +=========================== + +Bug-fix release. +---------------- + +Fix error handling in python bindings. + + The python bindings in 0.11 failed to detect NULL pointers being + returned from libnotmuch functions and thus failed to raise + exceptions to indicate the error condition. Any subsequent calls + into libnotmuch caused segmentation faults. + + Notmuch 0.11 (2012-01-13) ========================= -- cgit v1.2.3 From 649a9f5f3a3749374284dc757250345db8d5c7ad Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 23 Jan 2012 05:22:32 +0100 Subject: search: rename auto_exclude_tags to {search, }exclude_tags All other config-related functions and args include the section title in their name, so for the sake of consistency, mirror that. Also, the "auto"matic part is a given, so that was dropped. --- notmuch-client.h | 4 ++-- notmuch-config.c | 28 ++++++++++++++-------------- notmuch-count.c | 12 ++++++------ notmuch-search.c | 12 ++++++------ 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 9c1d383..f5414f6 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -252,10 +252,10 @@ notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config, notmuch_bool_t synchronize_flags); const char ** -notmuch_config_get_auto_exclude_tags (notmuch_config_t *config, size_t *length); +notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length); void -notmuch_config_set_auto_exclude_tags (notmuch_config_t *config, +notmuch_config_set_search_exclude_tags (notmuch_config_t *config, const char *list[], size_t length); diff --git a/notmuch-config.c b/notmuch-config.c index 8dcfe86..39da888 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -89,7 +89,7 @@ static const char search_config_comment[] = "\n" " The following option is supported here:\n" "\n" - "\tauto_exclude_tags\n" + "\texclude_tags\n" "\t\tA ;-separated list of tags that will be excluded from\n" "\t\tsearch results by default. Using an excluded tag in a\n" "\t\tquery will override that exclusion.\n"; @@ -106,8 +106,8 @@ struct _notmuch_config { const char **new_tags; size_t new_tags_length; notmuch_bool_t maildir_synchronize_flags; - const char **auto_exclude_tags; - size_t auto_exclude_tags_length; + const char **search_exclude_tags; + size_t search_exclude_tags_length; }; static int @@ -265,8 +265,8 @@ notmuch_config_open (void *ctx, config->new_tags = NULL; config->new_tags_length = 0; config->maildir_synchronize_flags = TRUE; - config->auto_exclude_tags = NULL; - config->auto_exclude_tags_length = 0; + config->search_exclude_tags = NULL; + config->search_exclude_tags_length = 0; if (! g_key_file_load_from_file (config->key_file, config->filename, @@ -361,9 +361,9 @@ notmuch_config_open (void *ctx, notmuch_config_set_new_tags (config, tags, 2); } - if (notmuch_config_get_auto_exclude_tags (config, &tmp) == NULL) { + if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) { const char *tags[] = { "deleted", "spam" }; - notmuch_config_set_auto_exclude_tags (config, tags, 2); + notmuch_config_set_search_exclude_tags (config, tags, 2); } error = NULL; @@ -624,20 +624,20 @@ notmuch_config_set_new_tags (notmuch_config_t *config, } const char ** -notmuch_config_get_auto_exclude_tags (notmuch_config_t *config, size_t *length) +notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length) { - return _config_get_list (config, "search", "auto_exclude_tags", - &(config->auto_exclude_tags), - &(config->auto_exclude_tags_length), length); + return _config_get_list (config, "search", "exclude_tags", + &(config->search_exclude_tags), + &(config->search_exclude_tags_length), length); } void -notmuch_config_set_auto_exclude_tags (notmuch_config_t *config, +notmuch_config_set_search_exclude_tags (notmuch_config_t *config, const char *list[], size_t length) { - _config_set_list (config, "search", "auto_exclude_tags", list, length, - &(config->auto_exclude_tags)); + _config_set_list (config, "search", "exclude_tags", list, length, + &(config->search_exclude_tags)); } /* Given a configuration item of the form . return the diff --git a/notmuch-count.c b/notmuch-count.c index f77861e..63459fb 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -35,8 +35,8 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) char *query_str; int opt_index; int output = OUTPUT_MESSAGES; - const char **auto_exclude_tags; - size_t auto_exclude_tags_length; + const char **search_exclude_tags; + size_t search_exclude_tags_length; unsigned int i; notmuch_opt_desc_t options[] = { @@ -78,10 +78,10 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) return 1; } - auto_exclude_tags = notmuch_config_get_auto_exclude_tags - (config, &auto_exclude_tags_length); - for (i = 0; i < auto_exclude_tags_length; i++) - notmuch_query_add_tag_exclude (query, auto_exclude_tags[i]); + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); switch (output) { case OUTPUT_MESSAGES: diff --git a/notmuch-search.c b/notmuch-search.c index 8867aab..d504051 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -423,8 +423,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) output_t output = OUTPUT_SUMMARY; int offset = 0; int limit = -1; /* unlimited */ - const char **auto_exclude_tags; - size_t auto_exclude_tags_length; + const char **search_exclude_tags; + size_t search_exclude_tags_length; unsigned int i; enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT } @@ -493,10 +493,10 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) notmuch_query_set_sort (query, sort); - auto_exclude_tags = notmuch_config_get_auto_exclude_tags - (config, &auto_exclude_tags_length); - for (i = 0; i < auto_exclude_tags_length; i++) - notmuch_query_add_tag_exclude (query, auto_exclude_tags[i]); + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); switch (output) { default: -- cgit v1.2.3 From 6795c62a50815786fd168536371cf6b9ed817535 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 23 Jan 2012 05:22:33 +0100 Subject: test: only exclude "deleted" messages from search if explicitly configured Currently, the 'search.exclude_tags' option is automatically set to "deleted;spam;" if it's missing from the config file. This violates the Principle of Least Surprise, so update the tests to *only* expect the exclusion of messages which are tagged "deleted" if the 'search.exclude_tags' option is explicitly set *and* contains that tag. --- test/search | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/search b/test/search index bf965e7..99d94bd 100755 --- a/test/search +++ b/test/search @@ -130,6 +130,7 @@ output=$(notmuch search "bödý" | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)" test_begin_subtest "Exclude \"deleted\" messages from search" +notmuch config set search.exclude_tags = deleted generate_message '[subject]="Not deleted"' generate_message '[subject]="Deleted"' notmuch new > /dev/null @@ -147,4 +148,11 @@ output=$(notmuch search subject:deleted | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)" +test_begin_subtest "Don't exclude \"deleted\" messages from search if not configured" +test_subtest_known_broken +notmuch config set search.exclude_tags +output=$(notmuch search subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) +thread:XXX 2001-01-05 [2/2] Notmuch Test Suite; Deleted (deleted inbox unread)" + test_done -- cgit v1.2.3 From ba33a15ec3ab80b175cf54d2584aa8acd1a2dc6e Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 23 Jan 2012 05:22:34 +0100 Subject: config: only exclude messages if 'search.exclude_tags' is explicitly set Currently, the 'search.exclude_tags' option is automatically set to "deleted;spam;" if it's missing from the config file. This violates the Principle of Least Surprise, so *only* set 'search.exclude_tags' to "deleted;spam;" if we didn't find a configuration file at all. This patch is actually Austin Clements' work: id:"20120117203211.GQ16740@mit.edu" --- notmuch-config.c | 8 ++++++-- test/search | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/notmuch-config.c b/notmuch-config.c index 39da888..0ded6d7 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -362,8 +362,12 @@ notmuch_config_open (void *ctx, } if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) { - const char *tags[] = { "deleted", "spam" }; - notmuch_config_set_search_exclude_tags (config, tags, 2); + if (is_new) { + const char *tags[] = { "deleted", "spam" }; + notmuch_config_set_search_exclude_tags (config, tags, 2); + } else { + notmuch_config_set_search_exclude_tags (config, NULL, 0); + } } error = NULL; diff --git a/test/search b/test/search index 99d94bd..414be35 100755 --- a/test/search +++ b/test/search @@ -149,7 +149,6 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; N thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)" test_begin_subtest "Don't exclude \"deleted\" messages from search if not configured" -test_subtest_known_broken notmuch config set search.exclude_tags output=$(notmuch search subject:deleted | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) -- cgit v1.2.3 From ed6f941c2a17040443a15bf18ed614fa45208dad Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 23 Jan 2012 06:50:45 +0100 Subject: setup: Create functions for tag list printing and parsing This refactors the tag list printing and parsing currently used for new.tags so that both can be reused for the new search.exclude_tags option. --- notmuch-setup.c | 55 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/notmuch-setup.c b/notmuch-setup.c index c3ea937..f85e0eb 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -87,6 +87,38 @@ welcome_message_post_setup (void) "have sufficient storage space available now.\n\n"); } +static void +print_tag_list (const char **tags, size_t tags_len) +{ + unsigned int i; + for (i = 0; i < tags_len; i++) { + if (i != 0) + printf (" "); + printf ("%s", tags[i]); + } +} + +static GPtrArray * +parse_tag_list (void *ctx, char *response) +{ + GPtrArray *tags = g_ptr_array_new (); + char *tag = response; + char *space; + + while (tag && *tag) { + space = strchr (tag, ' '); + if (space) + g_ptr_array_add (tags, talloc_strndup (ctx, tag, space - tag)); + else + g_ptr_array_add (tags, talloc_strdup (ctx, tag)); + tag = space; + while (tag && *tag == ' ') + tag++; + } + + return tags; +} + int notmuch_setup_command (unused (void *ctx), unused (int argc), unused (char *argv[])) @@ -164,30 +196,11 @@ notmuch_setup_command (unused (void *ctx), new_tags = notmuch_config_get_new_tags (config, &new_tags_len); printf ("Tags to apply to all new messages (separated by spaces) ["); - - for (i = 0; i < new_tags_len; i++) { - if (i != 0) - printf (" "); - printf ("%s", new_tags[i]); - } - + print_tag_list (new_tags, new_tags_len); prompt ("]: "); if (strlen (response)) { - GPtrArray *tags = g_ptr_array_new (); - char *tag = response; - char *space; - - while (tag && *tag) { - space = strchr (tag, ' '); - if (space) - g_ptr_array_add (tags, talloc_strndup (ctx, tag, space - tag)); - else - g_ptr_array_add (tags, talloc_strdup (ctx, tag)); - tag = space; - while (tag && *tag == ' ') - tag++; - } + GPtrArray *tags = parse_tag_list (ctx, response); notmuch_config_set_new_tags (config, (const char **) tags->pdata, tags->len); -- cgit v1.2.3 From e1da28742da4abda9b5d1a737291768322bbbe18 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 23 Jan 2012 06:40:33 +0100 Subject: setup: prompt user for search.exclude_tags value Allow users to customize the search.exclude_tags option during setup. --- notmuch-setup.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/notmuch-setup.c b/notmuch-setup.c index f85e0eb..94d0aa7 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -133,6 +133,8 @@ notmuch_setup_command (unused (void *ctx), int is_new; const char **new_tags; size_t new_tags_len; + const char **search_exclude_tags; + size_t search_exclude_tags_len; #define prompt(format, ...) \ do { \ @@ -208,6 +210,24 @@ notmuch_setup_command (unused (void *ctx), g_ptr_array_free (tags, TRUE); } + + search_exclude_tags = notmuch_config_get_search_exclude_tags (config, &search_exclude_tags_len); + + printf ("Tags to exclude when searching messages (separated by spaces) ["); + print_tag_list (search_exclude_tags, search_exclude_tags_len); + prompt ("]: "); + + if (strlen (response)) { + GPtrArray *tags = parse_tag_list (ctx, response); + + notmuch_config_set_search_exclude_tags (config, + (const char **) tags->pdata, + tags->len); + + g_ptr_array_free (tags, TRUE); + } + + if (! notmuch_config_save (config)) { if (is_new) welcome_message_post_setup (); -- cgit v1.2.3 From 08b3b0f37f73922d84943fe27a36e1964266c674 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Mon, 23 Jan 2012 06:41:45 +0100 Subject: NEWS: update "Tag exclusion" section --- NEWS | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 2c2d9e9..2acdce5 100644 --- a/NEWS +++ b/NEWS @@ -13,10 +13,21 @@ Reply to sender Tag exclusion - Tags can be automatically excluded from search results unless they - appear explicitly in a query. By default, notmuch excludes the tags - deleted and spam. This can be changed using the new config setting - search.auto_exclude_tags. + Tags can be automatically excluded from search results by adding them + to the new 'search.exclude_tags' option in the Notmuch config file. + + This behaviour can be overridden by explicitly including an excluded + tag in your query, for example: + + notmuch search $your_query and tag:$excluded_tag + + Existing users will probably want to run "notmuch setup" again to add + the new well-commented [search] section to the configuration file. + + For new configurations, accepting the default setting will cause the + tags "deleted" and "spam" to be excluded, equivalent to running: + + notmuch config set search.exclude_tags deleted spam Emacs Interface --------------- -- cgit v1.2.3 From 4ba787bca2ce668d9c8c3465436d9f581461738c Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 17 Jan 2012 10:54:26 -0800 Subject: emacs: have notmuch-search-archive-thread use -next-thread function Use this standard function, to keep thread navigation in one place. --- emacs/notmuch.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 6b2c252..3ec0816 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -625,7 +625,7 @@ thread or threads in the current region." This function advances the next thread when finished." (interactive) (notmuch-search-remove-tag-thread "inbox") - (forward-line)) + (notmuch-search-next-thread)) (defvar notmuch-search-process-filter-data nil "Data that has not yet been processed.") -- cgit v1.2.3 From a66e65d604c5e658daa97acbb9d0487788863521 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 23 Jan 2012 18:33:09 -0500 Subject: mime node: Record depth-first part numbers This makes the part numbers readily accessible to formatters. Hierarchical part numbering would be a more natural and efficient fit for MIME and may be the way to go in the future, but depth-first numbering maintains compatibility with what we currently do. --- mime-node.c | 38 +++++++++++++++++++++++++++++++++++--- notmuch-client.h | 14 +++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/mime-node.c b/mime-node.c index 27077f7..d6b4506 100644 --- a/mime-node.c +++ b/mime-node.c @@ -112,6 +112,11 @@ mime_node_open (const void *ctx, notmuch_message_t *message, root->nchildren = 1; root->ctx = mctx; + root->parent = NULL; + root->part_num = 0; + root->next_child = 0; + root->next_part_num = 1; + *root_out = root; return NOTMUCH_STATUS_SUCCESS; @@ -137,7 +142,7 @@ _signature_validity_free (GMimeSignatureValidity **proxy) #endif static mime_node_t * -_mime_node_create (const mime_node_t *parent, GMimeObject *part) +_mime_node_create (mime_node_t *parent, GMimeObject *part) { mime_node_t *node = talloc_zero (parent, mime_node_t); GError *err = NULL; @@ -150,6 +155,9 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) talloc_free (node); return NULL; } + node->parent = parent; + node->part_num = node->next_part_num = -1; + node->next_child = 0; /* Deal with the different types of parts */ if (GMIME_IS_PART (part)) { @@ -267,9 +275,10 @@ _mime_node_create (const mime_node_t *parent, GMimeObject *part) } mime_node_t * -mime_node_child (const mime_node_t *parent, int child) +mime_node_child (mime_node_t *parent, int child) { GMimeObject *sub; + mime_node_t *node; if (!parent || child < 0 || child >= parent->nchildren) return NULL; @@ -287,7 +296,30 @@ mime_node_child (const mime_node_t *parent, int child) INTERNAL_ERROR ("Unexpected GMimeObject type: %s", g_type_name (G_OBJECT_TYPE (parent->part))); } - return _mime_node_create (parent, sub); + node = _mime_node_create (parent, sub); + + if (child == parent->next_child && parent->next_part_num != -1) { + /* We're traversing in depth-first order. Record the child's + * depth-first numbering. */ + node->part_num = parent->next_part_num; + node->next_part_num = node->part_num + 1; + + /* Prepare the parent for its next depth-first child. */ + parent->next_child++; + parent->next_part_num = -1; + + if (node->nchildren == 0) { + /* We've reached a leaf, so find the parent that has more + * children and set it up to number its next child. */ + mime_node_t *iter = node->parent; + while (iter && iter->next_child == iter->nchildren) + iter = iter->parent; + if (iter) + iter->next_part_num = node->part_num + 1; + } + } + + return node; } static mime_node_t * diff --git a/notmuch-client.h b/notmuch-client.h index f5414f6..70f2336 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -297,6 +297,13 @@ typedef struct mime_node { /* The number of children of this part. */ int nchildren; + /* The parent of this node or NULL if this is the root node. */ + struct mime_node *parent; + + /* The depth-first part number of this child if the MIME tree is + * being traversed in depth-first order, or -1 otherwise. */ + int part_num; + /* True if decryption of this part was attempted. */ notmuch_bool_t decrypt_attempted; /* True if decryption of this part's child succeeded. In this @@ -324,6 +331,11 @@ typedef struct mime_node { /* Internal: For successfully decrypted multipart parts, the * decrypted part to substitute for the second child. */ GMimeObject *decrypted_child; + + /* Internal: The next child for depth-first traversal and the part + * number to assign it (or -1 if unknown). */ + int next_child; + int next_part_num; } mime_node_t; /* Construct a new MIME node pointing to the root message part of @@ -356,7 +368,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message, * an error message on stderr). */ mime_node_t * -mime_node_child (const mime_node_t *parent, int child); +mime_node_child (mime_node_t *parent, int child); /* Return the nth child of node in a depth-first traversal. If n is * 0, returns node itself. Returns NULL if there is no such part. */ -- cgit v1.2.3 From 7430a42e23ee775818f84ed75f417302da694152 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 23 Jan 2012 18:33:10 -0500 Subject: show: Introduce mime_node formatter callback This callback is the gateway to the new mime_node_t-based formatters. This maintains backwards compatibility so the formatters can be transitioned one at a time. Once all formatters are converted, the formatter structure can be reduced to only message_set_{start,sep,end} and part, most of show_message can be deleted, and all of show-message.c can be deleted. --- notmuch-client.h | 6 ++++++ notmuch-reply.c | 2 +- notmuch-show.c | 21 +++++++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 70f2336..e0eb594 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -62,8 +62,14 @@ #define STRINGIFY(s) STRINGIFY_(s) #define STRINGIFY_(s) #s +struct mime_node; +struct notmuch_show_params; + typedef struct notmuch_show_format { const char *message_set_start; + void (*part) (const void *ctx, + struct mime_node *node, int indent, + const struct notmuch_show_params *params); const char *message_start; void (*message) (const void *ctx, notmuch_message_t *message, diff --git a/notmuch-reply.c b/notmuch-reply.c index bf67960..f55b1d2 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -31,7 +31,7 @@ static void reply_part_content (GMimeObject *part); static const notmuch_show_format_t format_reply = { - "", + "", NULL, "", NULL, "", NULL, reply_headers_message_part, ">\n", "", diff --git a/notmuch-show.c b/notmuch-show.c index 682aa71..dec799c 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -42,7 +42,7 @@ static void format_part_end_text (GMimeObject *part); static const notmuch_show_format_t format_text = { - "", + "", NULL, "\fmessage{ ", format_message_text, "\fheader{\n", format_headers_text, format_headers_message_part_text, "\fheader}\n", "\fbody{\n", @@ -89,7 +89,7 @@ static void format_part_end_json (GMimeObject *part); static const notmuch_show_format_t format_json = { - "[", + "[", NULL, "{", format_message_json, "\"headers\": {", format_headers_json, format_headers_message_part_json, "}", ", \"body\": [", @@ -110,7 +110,7 @@ format_message_mbox (const void *ctx, unused (int indent)); static const notmuch_show_format_t format_mbox = { - "", + "", NULL, "", format_message_mbox, "", NULL, NULL, "", "", @@ -129,7 +129,7 @@ static void format_part_content_raw (GMimeObject *part); static const notmuch_show_format_t format_raw = { - "", + "", NULL, "", NULL, "", NULL, format_headers_message_part_text, "\n", "", @@ -850,6 +850,19 @@ show_message (void *ctx, int indent, notmuch_show_params_t *params) { + if (format->part) { + void *local = talloc_new (ctx); + mime_node_t *root, *part; + + if (mime_node_open (local, message, params->cryptoctx, params->decrypt, + &root) == NOTMUCH_STATUS_SUCCESS && + (part = mime_node_seek_dfs (root, (params->part < 0 ? + 0 : params->part)))) + format->part (local, part, indent, params); + talloc_free (local); + return; + } + if (params->part <= 0) { fputs (format->message_start, stdout); if (format->message) -- cgit v1.2.3 From 2903b32f79dcf7789a2b015c48194cb40ac71462 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Tue, 24 Jan 2012 16:14:04 +0000 Subject: test: Don't return the result of checking for running emacs to the tester. When checking for a running emacs, test_emacs evaluates the empty list '()'. This returns 'nil' when emacs is running, which is then prepended to the actual test result. Given that it is not part of the actual test output the test harness can incorrectly report test failure (or success). --- test/test-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 0da60fb..82c686c 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -926,7 +926,7 @@ test_emacs () { --eval '(orphan-watchdog $$)'" || return EMACS_SERVER="$server_name" # wait until the emacs server is up - until test_emacs '()' 2>/dev/null; do + until test_emacs '()' >/dev/null 2>/dev/null; do sleep 1 done fi -- cgit v1.2.3 From 260975e8aff635b6b99db835ee8d40ec33ee916c Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Tue, 24 Jan 2012 16:14:05 +0000 Subject: test: Add `test_emacs_expect_t'. Add a new test function to allow simpler testing of emacs functionality. `test_emacs_expect_t' takes one argument - a lisp expression to evaluate. The test passes if the expression returns `t', otherwise it fails and the output is reported to the tester. --- test/README | 8 ++++++++ test/emacs-test-functions.sh | 9 +++++++++ test/notmuch-test | 1 + test/test-lib.el | 9 +++++++++ test/test-lib.sh | 29 +++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+) create mode 100755 test/emacs-test-functions.sh diff --git a/test/README b/test/README index 44ff653..43656a3 100644 --- a/test/README +++ b/test/README @@ -202,6 +202,14 @@ library for your script to use. tests that may run in the same Emacs instance. Use `let' instead so the scope of the changed variables is limited to a single test. + test_emacs_expect_t + + This function executes the provided emacs lisp script within + emacs in a manner similar to 'test_emacs'. The expressions should + return the value `t' to indicate that the test has passed. If the + test does not return `t' then it is considered failed and all data + returned by the test is reported to the tester. + test_done Your test script must have test_done at the end. Its purpose diff --git a/test/emacs-test-functions.sh b/test/emacs-test-functions.sh new file mode 100755 index 0000000..0e1f9fc --- /dev/null +++ b/test/emacs-test-functions.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +test_description="emacs test function sanity" +. test-lib.sh + +test_begin_subtest "emacs test function sanity" +test_emacs_expect_t 't' + +test_done diff --git a/test/notmuch-test b/test/notmuch-test index 6a99ae3..d034f99 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -52,6 +52,7 @@ TESTS=" python hooks argument-parsing + emacs-test-functions.sh " TESTS=${NOTMUCH_TESTS:=$TESTS} diff --git a/test/test-lib.el b/test/test-lib.el index 59c5868..96752f0 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -83,3 +83,12 @@ nothing." (add-hook-counter 'notmuch-hello-mode-hook) (add-hook-counter 'notmuch-hello-refresh-hook) + +(defmacro notmuch-test-run (&rest body) + "Evaluate a BODY of test expressions and output the result." + `(with-temp-buffer + (let ((result (progn ,@body))) + (insert (if (stringp result) + result + (prin1-to-string result))) + (test-output)))) diff --git a/test/test-lib.sh b/test/test-lib.sh index 82c686c..8158328 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -503,6 +503,35 @@ test_expect_equal_file () fi } +test_emacs_expect_t () { + test "$#" = 2 && { prereq=$1; shift; } || prereq= + test "$#" = 1 || + error "bug in the test script: not 1 or 2 parameters to test_emacs_expect_t" + + # Run the test. + if ! test_skip "$test_subtest_name" + then + test_emacs "(notmuch-test-run $1)" >/dev/null + + # Restore state after the test. + exec 1>&6 2>&7 # Restore stdout and stderr + inside_subtest= + + # Report success/failure. + result=$(cat OUTPUT) + if [ "$result" = t ] + then + test_ok_ "$test_subtest_name" + else + test_failure_ "$test_subtest_name" "${result}" + fi + else + # Restore state after the (non) test. + exec 1>&6 2>&7 # Restore stdout and stderr + inside_subtest= + fi +} + NOTMUCH_NEW () { notmuch new | grep -v -E -e '^Processed [0-9]*( total)? file|Found [0-9]* total file' -- cgit v1.2.3 From f92d7dee8fcf399bda361df82187523598bb09b4 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Tue, 24 Jan 2012 16:14:06 +0000 Subject: test: Add more helpers for emacs tests. --- test/test-lib.el | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/test-lib.el b/test/test-lib.el index 96752f0..bc75f06 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -20,6 +20,8 @@ ;; ;; Authors: Dmitry Kurochkin +(require 'cl) ;; This code is generally used uncompiled. + ;; `read-file-name' by default uses `completing-read' function to read ;; user input. It does not respect `standard-input' variable which we ;; use in tests to provide user input. So replace it with a plain @@ -92,3 +94,27 @@ nothing." result (prin1-to-string result))) (test-output)))) + +(defun notmuch-test-report-unexpected (output expected) + "Report that the OUTPUT does not match the EXPECTED result." + (concat "Expect:\t" (prin1-to-string expected) "\n" + "Output:\t" (prin1-to-string output) "\n")) + +(defun notmuch-test-expect-equal (output expected) + "Compare OUTPUT with EXPECTED. Report any discrepencies." + (if (equal output expected) + t + (cond + ((and (listp output) + (listp expected)) + ;; Reporting the difference between two lists is done by + ;; reporting differing elements of OUTPUT and EXPECTED + ;; pairwise. This is expected to make analysis of failures + ;; simpler. + (apply #'concat (loop for o in output + for e in expected + if (not (equal o e)) + collect (notmuch-test-report-unexpected o e)))) + + (t + (notmuch-test-report-unexpected output expected))))) -- cgit v1.2.3 From 3699fedb3accf5fbd8085c0789d43c716d6ad3e9 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Tue, 24 Jan 2012 16:14:07 +0000 Subject: test: Add address cleaning tests. --- test/emacs-address-cleaning.el | 29 +++++++++++++++++++++++++++++ test/emacs-address-cleaning.sh | 19 +++++++++++++++++++ test/notmuch-test | 1 + 3 files changed, 49 insertions(+) create mode 100644 test/emacs-address-cleaning.el create mode 100755 test/emacs-address-cleaning.sh diff --git a/test/emacs-address-cleaning.el b/test/emacs-address-cleaning.el new file mode 100644 index 0000000..19e9e05 --- /dev/null +++ b/test/emacs-address-cleaning.el @@ -0,0 +1,29 @@ +(defun notmuch-test-address-cleaning-1 () + (notmuch-test-expect-equal (notmuch-show-clean-address "dme@dme.org") + "dme@dme.org")) + +(defun notmuch-test-address-cleaning-2 () + (let* ((input '("foo@bar.com" + "" + "Foo Bar " + "foo@bar.com " + "\"Foo Bar\" ")) + (expected '("foo@bar.com" + "foo@bar.com" + "Foo Bar " + "foo@bar.com" + "Foo Bar ")) + (output (mapcar #'notmuch-show-clean-address input))) + (notmuch-test-expect-equal output expected))) + +(defun notmuch-test-address-cleaning-3 () + (let* ((input '("ДБ " + "foo (at home) " + "foo [at home] " + "Foo Bar")) + (expected '("ДБ " + "foo (at home) " + "foo [at home] " + "Foo Bar")) + (output (mapcar #'notmuch-show-clean-address input))) + (notmuch-test-expect-equal output expected))) diff --git a/test/emacs-address-cleaning.sh b/test/emacs-address-cleaning.sh new file mode 100755 index 0000000..0d85bdc --- /dev/null +++ b/test/emacs-address-cleaning.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +test_description="emacs address cleaning" +. test-lib.sh + +test_begin_subtest "notmuch-test-address-clean part 1" +test_emacs_expect_t \ + '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-1)' + +test_begin_subtest "notmuch-test-address-clean part 2" +test_emacs_expect_t \ + '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-2)' + +test_begin_subtest "notmuch-test-address-clean part 3" +test_subtest_known_broken +test_emacs_expect_t \ + '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-3)' + +test_done diff --git a/test/notmuch-test b/test/notmuch-test index d034f99..3f1740c 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -53,6 +53,7 @@ TESTS=" hooks argument-parsing emacs-test-functions.sh + emacs-address-cleaning.sh " TESTS=${NOTMUCH_TESTS:=$TESTS} -- cgit v1.2.3 From 9e701465ebb43bcd5a56155be404758976e66c1f Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Tue, 24 Jan 2012 22:55:59 +0200 Subject: uncrustify.cfg: label indent, some known types, not, # and ## Adjusted some uncrustify variables to get closer to prevailing style: * Label indent (for goto) relative to current indentation. * Registered GMimeObject and mime_node_t being as types. * Space after ! (not) operator. * No space after 'stringify' (#) preprosessor token. * No spacing change around ## (option not versatile enough). There are at least 3 cases where attention needs to be paid: * If there is newline between function name and open paren in function call, the paren (and args) are indented too far right. * #define HOUR (60 *MINUTE) -- i.e. no space after star (*). * void (*foo)(args) -- i.e no space between (name) and (args). --- devel/uncrustify.cfg | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/devel/uncrustify.cfg b/devel/uncrustify.cfg index 1dbc5e4..d8075ba 100644 --- a/devel/uncrustify.cfg +++ b/devel/uncrustify.cfg @@ -16,8 +16,7 @@ input_tab_size = 8 # original tab size output_tab_size = 8 # new tab size indent_columns = 4 -indent_label = 2 # pos: absolute col, neg: relative column - +indent_label = -2 # pos: absolute col, neg: relative column # # inter-symbol newlines @@ -55,6 +54,11 @@ nl_after_struct = 0 # mod_full_brace_do = remove # "do a--; while ();" vs "do { a--; } while ();" # mod_full_brace_while = remove # "while (a) a--;" vs "while (a) { a--; }" +# +# Extra types used in notmuch source. +# (add more on demand) + +type GMimeObject mime_node_t # # inter-character spacing options @@ -63,6 +67,9 @@ nl_after_struct = 0 sp_before_ptr_star = force sp_between_ptr_star = remove sp_after_ptr_star = remove +sp_not = force +sp_pp_concat = ignore # XXX 'remove' drops leading space also +sp_pp_stringify = remove # sp _return_paren = force # "return (1);" vs "return(1);" sp_sizeof_paren = force # "sizeof (int)" vs "sizeof(int)" -- cgit v1.2.3 From 02d88159226b351091ae94e2176d2e59c79d21d3 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Wed, 25 Jan 2012 05:10:52 +0400 Subject: emacs: bind "s" to `notmuch-search' in notmuch-hello buffer Before the change, "s" in notmuch-hello buffer would jump to the search box. The patch changes the binding to `notmuch-search' which is consistent with all other notmuch buffers. --- emacs/notmuch-hello.el | 19 ++++++------------- test/emacs.expected-output/notmuch-hello | 2 +- .../notmuch-hello-no-saved-searches | 2 +- test/emacs.expected-output/notmuch-hello-with-empty | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 63f2e07..d88a870 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -29,9 +29,6 @@ (declare-function notmuch-search "notmuch" (query &optional oldest-first target-thread target-line continuation)) (declare-function notmuch-poll "notmuch" ()) -(defvar notmuch-hello-search-bar-marker nil - "The position of the search bar within the notmuch-hello buffer.") - (defcustom notmuch-recent-searches-max 10 "The number of recent searches to store and display." :type 'integer @@ -324,11 +321,6 @@ should be. Returns a cons cell `(tags-per-line width)'." (widget-insert "\n")) found-target-pos)) -(defun notmuch-hello-goto-search () - "Put point inside the `search' widget." - (interactive) - (goto-char notmuch-hello-search-bar-marker)) - (defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png"))) (defun notmuch-hello-search-continuation() @@ -358,7 +350,7 @@ should be. Returns a cons cell `(tags-per-line width)'." (define-key map "G" 'notmuch-hello-poll-and-update) (define-key map (kbd "") 'widget-backward) (define-key map "m" 'notmuch-mua-new-mail) - (define-key map "s" 'notmuch-hello-goto-search) + (define-key map "s" 'notmuch-search) map) "Keymap for \"notmuch hello\" buffers.") (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) @@ -471,7 +463,8 @@ Complete list of currently available key bindings: (widget-insert " messages.\n")) (let ((found-target-pos nil) - (final-target-pos nil)) + (final-target-pos nil) + (search-bar-pos)) (let* ((saved-alist ;; Filter out empty saved searches if required. (if notmuch-show-empty-saved-searches @@ -503,7 +496,7 @@ Complete list of currently available key bindings: (indent-rigidly start (point) notmuch-hello-indent))) (widget-insert "\nSearch: ") - (setq notmuch-hello-search-bar-marker (point-marker)) + (setq search-bar-pos (point-marker)) (widget-create 'editable-field ;; Leave some space at the start and end of the ;; search boxes. @@ -595,7 +588,7 @@ Complete list of currently available key bindings: (when notmuch-saved-searches (widget-insert "Edit saved searches with the `edit' button.\n")) (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") - (widget-insert "`=' refreshes this screen. `s' jumps to the search box. `q' to quit.\n") + (widget-insert "`=' refreshes this screen. `s' to search messages. `q' to quit.\n") (let ((fill-column (- (window-width) notmuch-hello-indent))) (center-region start (point)))) @@ -607,7 +600,7 @@ Complete list of currently available key bindings: (widget-forward 1))) (unless (widget-at) - (notmuch-hello-goto-search)))) + (goto-char search-bar-pos)))) (run-hooks 'notmuch-hello-refresh-hook)) diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index 196112e..c43ab8c 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -11,4 +11,4 @@ Search: . Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. - `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + `=' refreshes this screen. `s' to search messages. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index f4cfe49..080a56b 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -7,4 +7,4 @@ Search: . Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. - `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + `=' refreshes this screen. `s' to search messages. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index a860a72..a9e312c 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -11,4 +11,4 @@ Search: . Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. - `=' refreshes this screen. `s' jumps to the search box. `q' to quit. + `=' refreshes this screen. `s' to search messages. `q' to quit. -- cgit v1.2.3 From bc267b70b01c79f6bdda52641e9cd7574a151eff Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Wed, 25 Jan 2012 05:10:53 +0400 Subject: emacs: use a single history for all searches There are two ways to do search in Emacs UI: search widget in notmuch-hello buffer and `notmuch-search' function bound to "s". Before the change, these search mechanisms used different history lists. The patch makes notmuch-hello search use the same history list as `notmuch-search' function. --- emacs/notmuch-hello.el | 47 +++++++++++++++++++---------------------------- emacs/notmuch-lib.el | 3 +++ emacs/notmuch.el | 16 ++++++++++------ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index d88a870..6970bc3 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -29,8 +29,8 @@ (declare-function notmuch-search "notmuch" (query &optional oldest-first target-thread target-line continuation)) (declare-function notmuch-poll "notmuch" ()) -(defcustom notmuch-recent-searches-max 10 - "The number of recent searches to store and display." +(defcustom notmuch-hello-recent-searches-max 10 + "The number of recent searches to display." :type 'integer :group 'notmuch-hello) @@ -154,16 +154,6 @@ International Bureau of Weights and Measures." (defvar notmuch-hello-url "http://notmuchmail.org" "The `notmuch' web site.") -(defvar notmuch-hello-recent-searches nil) - -(defun notmuch-hello-remember-search (search) - (setq notmuch-hello-recent-searches - (delete search notmuch-hello-recent-searches)) - (push search notmuch-hello-recent-searches) - (if (> (length notmuch-hello-recent-searches) - notmuch-recent-searches-max) - (setq notmuch-hello-recent-searches (butlast notmuch-hello-recent-searches)))) - (defun notmuch-hello-nice-number (n) (let (result) (while (> n 0) @@ -183,9 +173,12 @@ International Bureau of Weights and Measures." search)) (defun notmuch-hello-search (search) - (let ((search (notmuch-hello-trim search))) - (notmuch-hello-remember-search search) - (notmuch-search search notmuch-search-oldest-first nil nil #'notmuch-hello-search-continuation))) + (unless (null search) + (setq search (notmuch-hello-trim search)) + (let ((history-delete-duplicates t)) + (add-to-history 'notmuch-search-history search))) + (notmuch-search search notmuch-search-oldest-first nil nil + #'notmuch-hello-search-continuation)) (defun notmuch-hello-add-saved-search (widget) (interactive) @@ -464,7 +457,7 @@ Complete list of currently available key bindings: (let ((found-target-pos nil) (final-target-pos nil) - (search-bar-pos)) + (default-pos)) (let* ((saved-alist ;; Filter out empty saved searches if required. (if notmuch-show-empty-saved-searches @@ -496,7 +489,7 @@ Complete list of currently available key bindings: (indent-rigidly start (point) notmuch-hello-indent))) (widget-insert "\nSearch: ") - (setq search-bar-pos (point-marker)) + (setq default-pos (point-marker)) (widget-create 'editable-field ;; Leave some space at the start and end of the ;; search boxes. @@ -513,18 +506,18 @@ Complete list of currently available key bindings: (put-text-property (1- (point)) (point) 'invisible t) (widget-insert "\n") - (when notmuch-hello-recent-searches + (when notmuch-search-history (widget-insert "\nRecent searches: ") (widget-create 'push-button :notify (lambda (&rest ignore) - (setq notmuch-hello-recent-searches nil) + (setq notmuch-search-history nil) (notmuch-hello-update)) "clear") (widget-insert "\n\n") - (let ((start (point)) - (nth 0)) - (mapc (lambda (search) - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth)))) + (let ((start (point))) + (loop for i from 1 to notmuch-hello-recent-searches-max + for search in notmuch-search-history do + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i)))) (set widget-symbol (widget-create 'editable-field ;; Don't let the search boxes be @@ -551,9 +544,7 @@ Complete list of currently available key bindings: (notmuch-hello-add-saved-search widget)) :notmuch-saved-search-widget widget-symbol "save")) - (widget-insert "\n") - (setq nth (1+ nth))) - notmuch-hello-recent-searches) + (widget-insert "\n")) (indent-rigidly start (point) notmuch-hello-indent))) (when alltags-alist @@ -582,7 +573,7 @@ Complete list of currently available key bindings: (let ((start (point))) (widget-insert "\n\n") (widget-insert "Type a search query and hit RET to view matching threads.\n") - (when notmuch-hello-recent-searches + (when notmuch-search-history (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") (widget-insert "Save recent searches with the `save' button.\n")) (when notmuch-saved-searches @@ -600,7 +591,7 @@ Complete list of currently available key bindings: (widget-forward 1))) (unless (widget-at) - (goto-char search-bar-pos)))) + (goto-char default-pos)))) (run-hooks 'notmuch-hello-refresh-hook)) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 241fe8c..c906ca7 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -69,6 +69,9 @@ ;; +(defvar notmuch-search-history nil + "Variable to store notmuch searches history.") + (defcustom notmuch-saved-searches nil "A list of saved searches to display." :type '(alist :key-type string :value-type string) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 3ec0816..e02966f 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -925,21 +925,25 @@ PROMPT is the string to prompt with." (t (list string))))))) ;; this was simpler than convincing completing-read to accept spaces: (define-key keymap (kbd "") 'minibuffer-complete) - (read-from-minibuffer prompt nil keymap nil - 'notmuch-query-history nil nil)))) + (let ((history-delete-duplicates t)) + (read-from-minibuffer prompt nil keymap nil + 'notmuch-search-history nil nil))))) ;;;###autoload -(defun notmuch-search (query &optional oldest-first target-thread target-line continuation) - "Run \"notmuch search\" with the given query string and display results. +(defun notmuch-search (&optional query oldest-first target-thread target-line continuation) + "Run \"notmuch search\" with the given `query' and display results. -The optional parameters are used as follows: +If `query' is nil, it is read interactively from the minibuffer. +Other optional parameters are used as follows: oldest-first: A Boolean controlling the sort order of returned threads target-thread: A thread ID (with the thread: prefix) that will be made current if it appears in the search results. target-line: The line number to move to if the target thread does not appear in the search results." - (interactive (list (notmuch-read-query "Notmuch search: "))) + (interactive) + (if (null query) + (setq query (notmuch-read-query "Notmuch search: "))) (let ((buffer (get-buffer-create (notmuch-search-buffer-title query)))) (switch-to-buffer buffer) (notmuch-search-mode) -- cgit v1.2.3 From e6e10b82c98fbb68db45a57d2167af6032934ebc Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Wed, 25 Jan 2012 05:10:54 +0400 Subject: emacs: bind "s" to `notmuch-hello-search' in notmuch-hello buffer `notmuch-hello-search' uses `notmuch-search' function but refreshes notmuch-hello buffer when the search buffer is closed. --- emacs/notmuch-hello.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index 6970bc3..ab65e36 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -172,7 +172,8 @@ International Bureau of Weights and Measures." (match-string 1 search) search)) -(defun notmuch-hello-search (search) +(defun notmuch-hello-search (&optional search) + (interactive) (unless (null search) (setq search (notmuch-hello-trim search)) (let ((history-delete-duplicates t)) @@ -343,7 +344,7 @@ should be. Returns a cons cell `(tags-per-line width)'." (define-key map "G" 'notmuch-hello-poll-and-update) (define-key map (kbd "") 'widget-backward) (define-key map "m" 'notmuch-mua-new-mail) - (define-key map "s" 'notmuch-search) + (define-key map "s" 'notmuch-hello-search) map) "Keymap for \"notmuch hello\" buffers.") (fset 'notmuch-hello-mode-map notmuch-hello-mode-map) -- cgit v1.2.3 From 63342a3c06cca53720f8be2ae8c4cf2c66d137f1 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Thu, 19 Jan 2012 09:34:07 +0000 Subject: emacs: Make the part content available to `mm-inlinable-p'. The `mm-inlinable-p' function works better if it has access to the data of the relevant part, so load that content before calling it. Don't load the content for parts that the user has indicated no desire to inline. This fixes the display of attached image/jpeg parts, for example. --- emacs/notmuch-show.el | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index e6a5b31..c37479a 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -429,14 +429,15 @@ current buffer, if possible." (with-temp-buffer (let* ((charset (plist-get part :content-charset)) (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset))))) - (if (and (mm-inlinable-p handle) - (mm-inlined-p handle)) - (let ((content (notmuch-show-get-bodypart-content msg part nth))) - (insert content) - (set-buffer display-buffer) - (mm-display-part handle) - t) - nil))))) + ;; If the user wants the part inlined, insert the content and + ;; test whether we are able to inline it (which includes both + ;; capability and suitability tests). + (when (mm-inlined-p handle) + (insert (notmuch-show-get-bodypart-content msg part nth)) + (when (mm-inlinable-p handle) + (set-buffer display-buffer) + (mm-display-part handle) + t)))))) (defvar notmuch-show-multipart/alternative-discouraged '( -- cgit v1.2.3 From 76f5da775e21b40aaa654e4a69f64770bb4801bd Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 25 Jan 2012 08:52:15 +0000 Subject: emacs: Fix a notmuch-print.el compiler warning. `notmuch-show-get-prop' should be declared. --- emacs/notmuch-print.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el index f96ccbe..fd86288 100644 --- a/emacs/notmuch-print.el +++ b/emacs/notmuch-print.el @@ -19,6 +19,8 @@ ;; ;; Authors: David Edmondson +(declare-function notmuch-show-get-prop "notmuch-show" (prop &optional props)) + (defcustom notmuch-print-mechanism 'notmuch-print-lpr "How should printing be done?" :group 'notmuch -- cgit v1.2.3 From d0a048f8561f63de5e30cb23dec6b6facb79c851 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Wed, 25 Jan 2012 22:24:56 +0400 Subject: emacs: polish notmuch-hello help text Make `=' binding description consistent with others. --- emacs/notmuch-hello.el | 2 +- test/emacs.expected-output/notmuch-hello | 2 +- test/emacs.expected-output/notmuch-hello-no-saved-searches | 2 +- test/emacs.expected-output/notmuch-hello-with-empty | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index ab65e36..d17a30f 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -580,7 +580,7 @@ Complete list of currently available key bindings: (when notmuch-saved-searches (widget-insert "Edit saved searches with the `edit' button.\n")) (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") - (widget-insert "`=' refreshes this screen. `s' to search messages. `q' to quit.\n") + (widget-insert "`=' to refresh this screen. `s' to search messages. `q' to quit.\n") (let ((fill-column (- (window-width) notmuch-hello-indent))) (center-region start (point)))) diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index c43ab8c..3e59595 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -11,4 +11,4 @@ Search: . Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. - `=' refreshes this screen. `s' to search messages. `q' to quit. + `=' to refresh this screen. `s' to search messages. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index 080a56b..ef0e5d0 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -7,4 +7,4 @@ Search: . Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. - `=' refreshes this screen. `s' to search messages. `q' to quit. + `=' to refresh this screen. `s' to search messages. `q' to quit. diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index a9e312c..71edba7 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -11,4 +11,4 @@ Search: . Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. - `=' refreshes this screen. `s' to search messages. `q' to quit. + `=' to refresh this screen. `s' to search messages. `q' to quit. -- cgit v1.2.3 From c70c7f86b83601231bac94db8a6596a1d729ae24 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Thu, 26 Jan 2012 07:19:37 +0000 Subject: test: `visible-buffer-substring' should not return text properties. When using `visible-buffer-substring' to examine a buffer, the text properties are not useful, so don't include them. --- test/test-lib.el | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test-lib.el b/test/test-lib.el index bc75f06..5b32e0a 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -51,16 +51,19 @@ FILENAME is OUTPUT." (with-temp-file (or filename "OUTPUT") (insert text)))) (defun visible-buffer-string () - "Same as `buffer-string', but excludes invisible text." + "Same as `buffer-string', but excludes invisible text and +removes any text properties." (visible-buffer-substring (point-min) (point-max))) (defun visible-buffer-substring (start end) - "Same as `buffer-substring', but excludes invisible text." + "Same as `buffer-substring-no-properties', but excludes +invisible text." (let (str) (while (< start end) (let ((next-pos (next-char-property-change start end))) (when (not (invisible-p start)) - (setq str (concat str (buffer-substring start next-pos)))) + (setq str (concat str (buffer-substring-no-properties + start next-pos)))) (setq start next-pos))) str)) -- cgit v1.2.3 From 2f50524e27db2ca58f6543a2c73a5719d8d7f491 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Thu, 26 Jan 2012 07:19:38 +0000 Subject: test: `notmuch-test-run' should protect against buffer switching. The body of the test may cause the current buffer to change. Ensure that the output goes to the correct buffer by switching back before inserting it. --- test/test-lib.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test-lib.el b/test/test-lib.el index 5b32e0a..6271da2 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -92,7 +92,9 @@ nothing." (defmacro notmuch-test-run (&rest body) "Evaluate a BODY of test expressions and output the result." `(with-temp-buffer - (let ((result (progn ,@body))) + (let ((buffer (current-buffer)) + (result (progn ,@body))) + (switch-to-buffer buffer) (insert (if (stringp result) result (prin1-to-string result))) -- cgit v1.2.3 From f764bbd5446cb69b8e9ac0bbb2cebada0a87a86a Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Thu, 26 Jan 2012 21:34:48 +0400 Subject: emacs: add completion to "tag all" operation ("*" binding) The patch adds completion to "tag all" operation bound to "*" (`notmuch-search-operate-all' function). --- emacs/notmuch.el | 60 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index e02966f..291eca2 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -48,6 +48,7 @@ ;; required, but is available from http://notmuchmail.org). (eval-when-compile (require 'cl)) +(require 'crm) (require 'mm-view) (require 'message) @@ -75,12 +76,38 @@ For example: (defvar notmuch-query-history nil "Variable to store minibuffer history for notmuch queries") -(defun notmuch-select-tag-with-completion (prompt &rest search-terms) +(defun notmuch-tag-completions (&optional prefixes search-terms) (let ((tag-list - (with-output-to-string - (with-current-buffer standard-output - (apply 'call-process notmuch-command nil t nil "search-tags" search-terms))))) - (completing-read prompt (split-string tag-list "\n+" t) nil nil nil))) + (split-string + (with-output-to-string + (with-current-buffer standard-output + (apply 'call-process notmuch-command nil t + nil "search-tags" search-terms))) + "\n+" t))) + (if (null prefixes) + tag-list + (apply #'append + (mapcar (lambda (tag) + (mapcar (lambda (prefix) + (concat prefix tag)) prefixes)) + tag-list))))) + +(defun notmuch-select-tag-with-completion (prompt &rest search-terms) + (let ((tag-list (notmuch-tag-completions nil search-terms))) + (completing-read prompt tag-list))) + +(defun notmuch-select-tags-with-completion (prompt &optional prefixes &rest search-terms) + (let ((tag-list (notmuch-tag-completions prefixes search-terms)) + (crm-separator " ") + ;; By default, space is bound to "complete word" function. + ;; Re-bind it to insert a space instead. Note that + ;; still does the completion. + (crm-local-completion-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map crm-local-completion-map) + (define-key map " " 'self-insert-command) + map))) + (delete "" (completing-read-multiple prompt tag-list)))) (defun notmuch-foreach-mime-part (function mm-handle) (cond ((stringp (car mm-handle)) @@ -849,7 +876,7 @@ non-authors is found, assume that all of the authors match." (goto-char found-target))) (delete-process proc)))) -(defun notmuch-search-operate-all (action) +(defun notmuch-search-operate-all (&rest actions) "Add/remove tags from all matching messages. This command adds or removes tags from all messages matching the @@ -860,16 +887,17 @@ will prompt for tags to be added or removed. Tags prefixed with Each character of the tag name may consist of alphanumeric characters as well as `_.+-'. " - (interactive "sOperation (+add -drop): notmuch tag ") - (let ((action-split (split-string action " +"))) - ;; Perform some validation - (let ((words action-split)) - (when (null words) (error "No operation given")) - (while words - (unless (string-match-p "^[-+][-+_.[:word:]]+$" (car words)) - (error "Action must be of the form `+thistag -that_tag'")) - (setq words (cdr words)))) - (apply 'notmuch-tag notmuch-search-query-string action-split))) + (interactive (notmuch-select-tags-with-completion + "Operations (+add -drop): notmuch tag " + '("+" "-"))) + ;; Perform some validation + (let ((words actions)) + (when (null words) (error "No operations given")) + (while words + (unless (string-match-p "^[-+][-+_.[:word:]]+$" (car words)) + (error "Action must be of the form `+this_tag' or `-that_tag'")) + (setq words (cdr words)))) + (apply 'notmuch-tag notmuch-search-query-string actions)) (defun notmuch-search-buffer-title (query) "Returns the title for a buffer with notmuch search results." -- cgit v1.2.3 From 58d714e5ced2b6d16dcbd91589715e5ec52c97b4 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Thu, 26 Jan 2012 21:34:49 +0400 Subject: emacs: `notmuch-search-operate-all' code cleanup, no functional changes --- emacs/notmuch.el | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 291eca2..72f78ed 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -891,12 +891,11 @@ characters as well as `_.+-'. "Operations (+add -drop): notmuch tag " '("+" "-"))) ;; Perform some validation - (let ((words actions)) - (when (null words) (error "No operations given")) - (while words - (unless (string-match-p "^[-+][-+_.[:word:]]+$" (car words)) - (error "Action must be of the form `+this_tag' or `-that_tag'")) - (setq words (cdr words)))) + (when (null actions) (error "No operations given")) + (mapc (lambda (action) + (unless (string-match-p "^[-+][-+_.[:word:]]+$" action) + (error "Action must be of the form `+this_tag' or `-that_tag'"))) + actions) (apply 'notmuch-tag notmuch-search-query-string actions)) (defun notmuch-search-buffer-title (query) -- cgit v1.2.3 From 3f003a3ae0ee800cbbae7ac1daa72b3be55f9d83 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Thu, 26 Jan 2012 08:17:50 +0000 Subject: emacs: Re-enable line wrapping in `notmuch-show-mode'. Turn on `visual-line-mode' via a hook, so that those who so choose can avoid it. --- emacs/notmuch-show.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index c37479a..94d94ed 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -71,9 +71,10 @@ any given message." "A list of functions called to decorate the headers listed in `notmuch-message-headers'.") -(defcustom notmuch-show-hook nil +(defcustom notmuch-show-hook '(notmuch-show-turn-on-visual-line-mode) "Functions called after populating a `notmuch-show' buffer." :type 'hook + :options '(notmuch-show-turn-on-visual-line-mode) :group 'notmuch-show :group 'notmuch-hooks) @@ -133,6 +134,10 @@ indentation." ,@body) (kill-buffer buf))))) +(defun notmuch-show-turn-on-visual-line-mode () + "Enable Visual Line mode." + (visual-line-mode t)) + (defun notmuch-show-view-all-mime-parts () "Use external viewers to view all attachments from the current message." (interactive) -- cgit v1.2.3 From cbc4876a33d34be8cb6abad9303c366a6be142a8 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 25 Jan 2012 13:53:58 +0000 Subject: emacs: Avoid `mail-header-parse-address' in `notmuch-show-clean-address'. `mail-header-parse-address' expects un-decoded mailbox parts, which is not what we have at this point. Replace it with simple string deconstruction. Mark the corresponding test as no longer broken. Minor whitespace cleanup. --- emacs/notmuch-show.el | 50 ++++++++++++++++++++++++++++++------------ test/emacs-address-cleaning.sh | 1 - 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 94d94ed..acc2f5e 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -294,21 +294,43 @@ operation on the contents of the current buffer." "Try to clean a single email ADDRESS for display. Return unchanged ADDRESS if parsing fails." (condition-case nil - (let* ((parsed (mail-header-parse-address address)) - (address (car parsed)) - (name (cdr parsed))) - ;; Remove double quotes. They might be required during transport, - ;; but we don't need to see them. - (when name - (setq name (replace-regexp-in-string "\"" "" name))) + (let (p-name p-address) + ;; It would be convenient to use `mail-header-parse-address', + ;; but that expects un-decoded mailbox parts, whereas our + ;; mailbox parts are already decoded (and hence may contain + ;; UTF-8). Given that notmuch should handle most of the awkward + ;; cases, some simple string deconstruction should be sufficient + ;; here. + (cond + ;; "User " style. + ((string-match "\\(.*\\) <\\(.*\\)>" address) + (setq p-name (match-string 1 address) + p-address (match-string 2 address))) + + ;; "" style. + ((string-match "<\\(.*\\)>" address) + (setq p-address (match-string 1 address))) + + ;; Everything else. + (t + (setq p-address address))) + + ;; Remove outer double quotes. They might be required during + ;; transport, but we don't need to see them. + (when (and p-name + (string-match "^\"\\(.*\\)\"$" p-name)) + (setq p-name (match-string 1 p-name))) + ;; If the address is 'foo@bar.com ' then show just ;; 'foo@bar.com'. - (when (string= name address) - (setq name nil)) - - (if (not name) - address - (concat name " <" address ">"))) + (when (string= p-name p-address) + (setq p-name nil)) + + ;; If no name results, return just the address. + (if (not p-name) + p-address + ;; Otherwise format the name and address together. + (concat p-name " <" p-address ">"))) (error address))) (defun notmuch-show-insert-headerline (headers date tags depth) @@ -1423,7 +1445,7 @@ than only the current message." (interactive "P\nsPipe message to command: ") (let (shell-command) (if entire-thread - (setq shell-command + (setq shell-command (concat notmuch-command " show --format=mbox " (shell-quote-argument (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR ")) diff --git a/test/emacs-address-cleaning.sh b/test/emacs-address-cleaning.sh index 0d85bdc..51018fe 100755 --- a/test/emacs-address-cleaning.sh +++ b/test/emacs-address-cleaning.sh @@ -12,7 +12,6 @@ test_emacs_expect_t \ '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-2)' test_begin_subtest "notmuch-test-address-clean part 3" -test_subtest_known_broken test_emacs_expect_t \ '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-3)' -- cgit v1.2.3 From 50d65de394cd78bd215918e251646c32eb0c0e52 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 25 Jan 2012 13:53:59 +0000 Subject: test: Updated expected output for new `notmuch-show-clean-address'. --- test/emacs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/emacs b/test/emacs index f150d95..8ca4c8a 100755 --- a/test/emacs +++ b/test/emacs @@ -78,7 +78,7 @@ thread=$(notmuch search --output=threads subject:message-with-invalid-from) test_emacs "(notmuch-show \"$thread\") (test-output)" cat <EXPECTED -"Invalid " From" (2001-01-05) (inbox) +Invalid " From (2001-01-05) (inbox) Subject: message-with-invalid-from To: Notmuch Test Suite Date: Fri, 05 Jan 2001 15:43:57 +0000 -- cgit v1.2.3 From cd03f214470996ec03b126f86afafef5296fd879 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 25 Jan 2012 13:54:00 +0000 Subject: emacs: Another special case for `notmuch-show-clean-address'. Remove backslashes. --- emacs/notmuch-show.el | 14 +++++++++----- test/emacs-address-cleaning.el | 6 ++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index acc2f5e..84ac624 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -315,11 +315,15 @@ unchanged ADDRESS if parsing fails." (t (setq p-address address))) - ;; Remove outer double quotes. They might be required during - ;; transport, but we don't need to see them. - (when (and p-name - (string-match "^\"\\(.*\\)\"$" p-name)) - (setq p-name (match-string 1 p-name))) + ;; Remove elements of the mailbox part that are not relevant for + ;; display, even if they are required during transport. + (when p-name + ;; Outer double quotes. + (when (string-match "^\"\\(.*\\)\"$" p-name) + (setq p-name (match-string 1 p-name))) + + ;; Backslashes. + (setq p-name (replace-regexp-in-string "\\\\" "" p-name))) ;; If the address is 'foo@bar.com ' then show just ;; 'foo@bar.com'. diff --git a/test/emacs-address-cleaning.el b/test/emacs-address-cleaning.el index 19e9e05..3b0b109 100644 --- a/test/emacs-address-cleaning.el +++ b/test/emacs-address-cleaning.el @@ -20,10 +20,12 @@ (let* ((input '("ДБ " "foo (at home) " "foo [at home] " - "Foo Bar")) + "Foo Bar" + "Fred Dibna \\[extraordinaire\\] ")) (expected '("ДБ " "foo (at home) " "foo [at home] " - "Foo Bar")) + "Foo Bar" + "Fred Dibna [extraordinaire] ")) (output (mapcar #'notmuch-show-clean-address input))) (notmuch-test-expect-equal output expected))) -- cgit v1.2.3 From 6f388fa711188813d670aa086f2a6acdbeead69a Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 25 Jan 2012 13:48:33 +0000 Subject: emacs: Don't mark messages as "unsaved" when printing. `ps-print-buffer' notes that a buffer is unsaved unless `buffer-modified-p' returns `nil', so ensure that it does. --- emacs/notmuch-print.el | 1 + 1 file changed, 1 insertion(+) diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el index fd86288..880f96d 100644 --- a/emacs/notmuch-print.el +++ b/emacs/notmuch-print.el @@ -82,6 +82,7 @@ Optional OUTPUT allows passing a list of flags to muttprint." (defun notmuch-print-message (msg) "Print a message using the user-selected mechanism." + (set-buffer-modified-p nil) (funcall notmuch-print-mechanism msg)) (provide 'notmuch-print) -- cgit v1.2.3 From fe74e6bea303eeaa5eb2954a0cae9a26f9d917fd Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Mon, 30 Jan 2012 12:31:25 +0200 Subject: moved _config_(get|set)_list () functions earlier in the file Moved static functions _config_get_list () and _config_set_list () closer to the beginning of file so that their definition is known (without adding forward declarations) in upcoming changes. --- notmuch-config.c | 84 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/notmuch-config.c b/notmuch-config.c index 0ded6d7..a124e34 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -467,6 +467,48 @@ notmuch_config_save (notmuch_config_t *config) return 0; } +static const char ** +_config_get_list (notmuch_config_t *config, + const char *section, const char *key, + const char ***outlist, size_t *list_length, size_t *ret_length) +{ + assert(outlist); + + if (*outlist == NULL) { + + char **inlist = g_key_file_get_string_list (config->key_file, + section, key, list_length, NULL); + if (inlist) { + unsigned int i; + + *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1)); + + for (i = 0; i < *list_length; i++) + (*outlist)[i] = talloc_strdup (*outlist, inlist[i]); + + (*outlist)[i] = NULL; + + g_strfreev (inlist); + } + } + + if (ret_length) + *ret_length = *list_length; + + return *outlist; +} + +static void +_config_set_list (notmuch_config_t *config, + const char *group, const char *name, + const char *list[], + size_t length, const char ***config_var ) +{ + g_key_file_set_string_list (config->key_file, group, name, list, length); + talloc_free (*config_var); + *config_var = NULL; +} + const char * notmuch_config_get_database_path (notmuch_config_t *config) { @@ -551,37 +593,6 @@ notmuch_config_set_user_primary_email (notmuch_config_t *config, config->user_primary_email = NULL; } -static const char ** -_config_get_list (notmuch_config_t *config, - const char *section, const char *key, - const char ***outlist, size_t *list_length, size_t *ret_length) -{ - assert(outlist); - - if (*outlist == NULL) { - - char **inlist = g_key_file_get_string_list (config->key_file, - section, key, list_length, NULL); - if (inlist) { - unsigned int i; - - *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1)); - - for (i = 0; i < *list_length; i++) - (*outlist)[i] = talloc_strdup (*outlist, inlist[i]); - - (*outlist)[i] = NULL; - - g_strfreev (inlist); - } - } - - if (ret_length) - *ret_length = *list_length; - - return *outlist; -} - const char ** notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length) { @@ -598,17 +609,6 @@ notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length) &(config->new_tags_length), length); } -static void -_config_set_list (notmuch_config_t *config, - const char *group, const char *name, - const char *list[], - size_t length, const char ***config_var ) -{ - g_key_file_set_string_list (config->key_file, group, name, list, length); - talloc_free (*config_var); - *config_var = NULL; -} - void notmuch_config_set_user_other_email (notmuch_config_t *config, const char *list[], -- cgit v1.2.3 From 9b7e0dcb9a8bd8a5898d9dd68715f3a0d0e5280d Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:16 -0800 Subject: emacs: use search-next-thread to move to next thread in show mode We should always use the dedicated search mode navigation functions, in case navigation mechanics change down the line. --- emacs/notmuch-show.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 84ac624..6b8bd19 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -39,6 +39,7 @@ (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-fontify-headers "notmuch" nil) (declare-function notmuch-select-tag-with-completion "notmuch" (prompt &rest search-terms)) +(declare-function notmuch-search-next-thread "notmuch" nil) (declare-function notmuch-search-show-thread "notmuch" nil) (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date") @@ -1567,7 +1568,7 @@ argument, hide all of the messages." (if parent-buffer (progn (switch-to-buffer parent-buffer) - (forward-line) + (notmuch-search-next-thread) (if show-next (notmuch-search-show-thread)))))) -- cgit v1.2.3 From 047792102c133f02e043759e9b8399e98938ba7f Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:17 -0800 Subject: emacs: break up notmuch-show-archive-thread-internal into two more generally useful functions Break up notmuch-show-archive-thread-internal into two new functions: notmuch-show-tag-thread-internal: applies a tag to all messages in thread. If option remove flag is t, tags will be removed instead of added. notmuch-show-next-thread: moves to the next thread in the search result. If given a prefix, will show the next result, otherwise will just move to it in the search view. Two new interactive functions, notmuch-show-{add,remove}-tag-thread, are also added. Together, these provide a better suit of thread tagging and navigation tools. The higher level thread archiving functions are modified to use these new function. --- emacs/notmuch-show.el | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 6b8bd19..3302439 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1557,20 +1557,38 @@ argument, hide all of the messages." (interactive) (backward-button 1)) -(defun notmuch-show-archive-thread-internal (show-next) - ;; Remove the tag from the current set of messages. +(defun notmuch-show-tag-thread-internal (tag &optional remove) + "Add tag to the current set of messages. + +If the remove switch is given, tags will be removed instead of +added." (goto-char (point-min)) - (loop do (notmuch-show-remove-tag "inbox") - until (not (notmuch-show-goto-message-next))) - ;; Move to the next item in the search results, if any. + (let ((tag-function (if remove + 'notmuch-show-remove-tag + 'notmuch-show-add-tag))) + (loop do (funcall tag-function tag) + until (not (notmuch-show-goto-message-next))))) + +(defun notmuch-show-add-tag-thread (tag) + "Add tag to all messages in the current thread." + (interactive) + (notmuch-show-tag-thread-internal tag)) + +(defun notmuch-show-remove-tag-thread (tag) + "Remove tag from all messages in the current thread." + (interactive) + (notmuch-show-tag-thread-internal tag t)) + +(defun notmuch-show-next-thread (&optional show-next) + "Move to the next item in the search results, if any." + (interactive "P") (let ((parent-buffer notmuch-show-parent-buffer)) (notmuch-kill-this-buffer) - (if parent-buffer - (progn - (switch-to-buffer parent-buffer) - (notmuch-search-next-thread) - (if show-next - (notmuch-search-show-thread)))))) + (when parent-buffer + (switch-to-buffer parent-buffer) + (notmuch-search-next-thread) + (if show-next + (notmuch-search-show-thread))))) (defun notmuch-show-archive-thread () "Archive each message in thread, then show next thread from search. @@ -1584,12 +1602,14 @@ being delivered to the same thread. It does not archive the entire thread, but only the messages shown in the current buffer." (interactive) - (notmuch-show-archive-thread-internal t)) + (notmuch-show-remove-tag-thread "inbox") + (notmuch-show-next-thread t)) (defun notmuch-show-archive-thread-then-exit () "Archive each message in thread, then exit back to search results." (interactive) - (notmuch-show-archive-thread-internal nil)) + (notmuch-show-remove-tag-thread "inbox") + (notmuch-show-next-thread)) (defun notmuch-show-stash-cc () "Copy CC field of current message to kill-ring." -- cgit v1.2.3 From be05158b69d45b63999707f60b8ee0166438a944 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:18 -0800 Subject: emacs: break out thread navigation from notmuch-show-archive-thread This function is now just for archiving the current thread. A new function is created to archive-then-next. The 'a' key binding is updated accordingly. This will allow people to bind to the simple thread archiving function without the extra navigation. The archive-thread function now also takes a prefix to unarchive the current thread (ie. put the whole thread back in the inbox). --- emacs/notmuch-show.el | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 3302439..10b3ea9 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1076,7 +1076,7 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map "-" 'notmuch-show-remove-tag) (define-key map "+" 'notmuch-show-add-tag) (define-key map "x" 'notmuch-show-archive-thread-then-exit) - (define-key map "a" 'notmuch-show-archive-thread) + (define-key map "a" 'notmuch-show-archive-thread-then-next) (define-key map "N" 'notmuch-show-next-message) (define-key map "P" 'notmuch-show-previous-message) (define-key map "n" 'notmuch-show-next-open-message) @@ -1336,7 +1336,7 @@ thread from the search from which this thread was originally shown." (interactive) (if (notmuch-show-advance) - (notmuch-show-archive-thread))) + (notmuch-show-archive-thread-then-next))) (defun notmuch-show-rewind () "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]). @@ -1590,8 +1590,12 @@ added." (if show-next (notmuch-search-show-thread))))) -(defun notmuch-show-archive-thread () - "Archive each message in thread, then show next thread from search. +(defun notmuch-show-archive-thread (&optional unarchive) + "Archive each message in thread. + +If a prefix argument is given, the messages will be +\"unarchived\" (ie. the \"inbox\" tag will be added instead of +removed). Archive each message currently shown by removing the \"inbox\" tag from each. Then kill this buffer and show the next thread @@ -1601,14 +1605,21 @@ Note: This command is safe from any race condition of new messages being delivered to the same thread. It does not archive the entire thread, but only the messages shown in the current buffer." + (interactive "P") + (if unarchive + (notmuch-show-add-tag-thread "inbox") + (notmuch-show-remove-tag-thread "inbox"))) + +(defun notmuch-show-archive-thread-then-next () + "Archive each message in thread, then show next thread from search." (interactive) - (notmuch-show-remove-tag-thread "inbox") + (notmuch-show-archive-thread) (notmuch-show-next-thread t)) (defun notmuch-show-archive-thread-then-exit () "Archive each message in thread, then exit back to search results." (interactive) - (notmuch-show-remove-tag-thread "inbox") + (notmuch-show-archive-thread) (notmuch-show-next-thread)) (defun notmuch-show-stash-cc () -- cgit v1.2.3 From 436c98a9732ea66a134674d7ff4711b7cdd8bbf9 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:19 -0800 Subject: emacs: add message archiving functions This adds two new message archiving functions that parallel the thread archiving functions: notmuch-show-archive-message{,-then-next}. The former also takes a prefix argument to unarchive the message (ie. put back in inbox). --- emacs/notmuch-show.el | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 10b3ea9..2871350 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1622,6 +1622,23 @@ buffer." (notmuch-show-archive-thread) (notmuch-show-next-thread)) +(defun notmuch-show-archive-message (&optional unarchive) + "Archive the current message. + +If a prefix argument is given, the message will be +\"unarchived\" (ie. the \"inbox\" tag will be added instead of +removed)." + (interactive "P") + (if unarchive + (notmuch-show-add-tag "inbox") + (notmuch-show-remove-tag "inbox"))) + +(defun notmuch-show-archive-message-then-next () + "Archive the current message, then show the next open message in the current thread." + (interactive) + (notmuch-show-archive-message) + (notmuch-show-next-open-message)) + (defun notmuch-show-stash-cc () "Copy CC field of current message to kill-ring." (interactive) -- cgit v1.2.3 From cdb51decddc4b013a47e7164891c5bc9041f6fc3 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:20 -0800 Subject: emacs: add option to show-next-{, open-}message functions to pop out to parent buffer if at end This will allow for keybindings that achieve a smoother message processing flow by reducing the number of key presses needed for most common operations. --- emacs/notmuch-show.el | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 2871350..293c00b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1390,14 +1390,19 @@ any effects from previous calls to (with-current-notmuch-show-message (notmuch-mua-new-forward-message prompt-for-sender))) -(defun notmuch-show-next-message () - "Show the next message." - (interactive) +(defun notmuch-show-next-message (&optional pop-at-end) + "Show the next message. + +If a prefix argument is given and this is the last message in the +thread, navigate to the next thread in the parent search buffer." + (interactive "P") (if (notmuch-show-goto-message-next) (progn (notmuch-show-mark-read) (notmuch-show-message-adjust)) - (goto-char (point-max)))) + (if pop-at-end + (notmuch-show-next-thread) + (goto-char (point-max))))) (defun notmuch-show-previous-message () "Show the previous message." @@ -1406,9 +1411,13 @@ any effects from previous calls to (notmuch-show-mark-read) (notmuch-show-message-adjust)) -(defun notmuch-show-next-open-message () - "Show the next message." - (interactive) +(defun notmuch-show-next-open-message (&optional pop-at-end) + "Show the next open message. + +If a prefix argument is given and this is the last open message +in the thread, navigate to the next thread in the parent search +buffer." + (interactive "P") (let (r) (while (and (setq r (notmuch-show-goto-message-next)) (not (notmuch-show-message-visible-p)))) @@ -1416,7 +1425,9 @@ any effects from previous calls to (progn (notmuch-show-mark-read) (notmuch-show-message-adjust)) - (goto-char (point-max))))) + (if pop-at-end + (notmuch-show-next-thread) + (goto-char (point-max)))))) (defun notmuch-show-previous-open-message () "Show the previous message." -- cgit v1.2.3 From 0417c22d1121ae94ec0c39641254c4d820ae5946 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:21 -0800 Subject: emacs: use pop-at-end functionality in show-archive-message-then-next function This provides a smoother message processing flow by reducing the number of key presses needed for these common operations. --- emacs/notmuch-show.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 293c00b..9a40b0e 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1648,7 +1648,7 @@ removed)." "Archive the current message, then show the next open message in the current thread." (interactive) (notmuch-show-archive-message) - (notmuch-show-next-open-message)) + (notmuch-show-next-open-message t)) (defun notmuch-show-stash-cc () "Copy CC field of current message to kill-ring." -- cgit v1.2.3 From 4a5281b888be9bd19eb493d23e9d4b59270dd9e8 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:22 -0800 Subject: emacs: modify the default show-mode key bindings for archiving This changes the default key bindings for the 'a' key in notmuch-show mode. Instead of archiving the entire thread, it now just archives the current message, and then advance to the next open message (archive-message-then-next). 'A' is now bound to the previous archive-thread-then-next function. --- emacs/notmuch-show.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 9a40b0e..6d41057 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1076,7 +1076,8 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map "-" 'notmuch-show-remove-tag) (define-key map "+" 'notmuch-show-add-tag) (define-key map "x" 'notmuch-show-archive-thread-then-exit) - (define-key map "a" 'notmuch-show-archive-thread-then-next) + (define-key map "a" 'notmuch-show-archive-message-then-next) + (define-key map "A" 'notmuch-show-archive-thread-then-next) (define-key map "N" 'notmuch-show-next-message) (define-key map "P" 'notmuch-show-previous-message) (define-key map "n" 'notmuch-show-next-open-message) -- cgit v1.2.3 From 7cd907b69c3134b00f8766571b17b669bd8a80c2 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Tue, 24 Jan 2012 16:06:23 -0800 Subject: emacs: fix show-previous-message doc string --- emacs/notmuch-show.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 6d41057..de9421e 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1431,7 +1431,7 @@ buffer." (goto-char (point-max)))))) (defun notmuch-show-previous-open-message () - "Show the previous message." + "Show the previous open message." (interactive) (while (and (notmuch-show-goto-message-previous) (not (notmuch-show-message-visible-p)))) -- cgit v1.2.3 From 2dcd1e7234f9dd50ec41c7b09297f002c6572a86 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Wed, 1 Feb 2012 14:50:00 +0100 Subject: emacs: globally replace non-branching "(if COND (progn ..." with "(when ..." Less code, same results, without sacrificing readability. --- emacs/notmuch-show.el | 9 ++++----- emacs/notmuch-wash.el | 47 +++++++++++++++++++++++------------------------ emacs/notmuch.el | 28 +++++++++++++--------------- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index de9421e..0a945ea 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1364,11 +1364,10 @@ any effects from previous calls to ;; If a small number of lines from the previous message are ;; visible, realign so that the top of the current message is at ;; the top of the screen. - (if (<= (count-screen-lines (window-start) start-of-message) - next-screen-context-lines) - (progn - (goto-char (notmuch-show-message-top)) - (notmuch-show-message-adjust))) + (when (<= (count-screen-lines (window-start) start-of-message) + next-screen-context-lines) + (goto-char (notmuch-show-message-top)) + (notmuch-show-message-adjust)) ;; Move to the top left of the window. (goto-char (window-start))) (t diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 5c1e830..67143e5 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -336,30 +336,29 @@ patch and then guesses the extent of the patch, there is scope for error." (goto-char (point-min)) - (if (re-search-forward diff-file-header-re nil t) - (progn - (beginning-of-line -1) - (let ((patch-start (point)) - (patch-end (point-max)) - part) - (goto-char patch-start) - (if (or - ;; Patch ends with signature. - (re-search-forward notmuch-wash-signature-regexp nil t) - ;; Patch ends with bugtraq comment. - (re-search-forward "^\\*\\*\\* " nil t)) - (setq patch-end (match-beginning 0))) - (save-restriction - (narrow-to-region patch-start patch-end) - (setq part (plist-put part :content-type "inline-patch-fake-part")) - (setq part (plist-put part :content (buffer-string))) - (setq part (plist-put part :id -1)) - (setq part (plist-put part :filename - (notmuch-wash-subject-to-patch-filename - (plist-get - (plist-get msg :headers) :Subject)))) - (delete-region (point-min) (point-max)) - (notmuch-show-insert-bodypart nil part depth)))))) + (when (re-search-forward diff-file-header-re nil t) + (beginning-of-line -1) + (let ((patch-start (point)) + (patch-end (point-max)) + part) + (goto-char patch-start) + (if (or + ;; Patch ends with signature. + (re-search-forward notmuch-wash-signature-regexp nil t) + ;; Patch ends with bugtraq comment. + (re-search-forward "^\\*\\*\\* " nil t)) + (setq patch-end (match-beginning 0))) + (save-restriction + (narrow-to-region patch-start patch-end) + (setq part (plist-put part :content-type "inline-patch-fake-part")) + (setq part (plist-put part :content (buffer-string))) + (setq part (plist-put part :id -1)) + (setq part (plist-put part :filename + (notmuch-wash-subject-to-patch-filename + (plist-get + (plist-get msg :headers) :Subject)))) + (delete-region (point-min) (point-max)) + (notmuch-show-insert-bodypart nil part depth))))) ;; diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 72f78ed..5fa239a 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -673,17 +673,16 @@ This function advances the next thread when finished." (goto-char (point-max)) (if (eq status 'signal) (insert "Incomplete search results (search process was killed).\n")) - (if (eq status 'exit) - (progn - (if notmuch-search-process-filter-data - (insert (concat "Error: Unexpected output from notmuch search:\n" notmuch-search-process-filter-data))) - (insert "End of search results.") - (unless (= exit-status 0) - (insert (format " (process returned %d)" exit-status))) - (insert "\n") - (if (and atbob - (not (string= notmuch-search-target-thread "found"))) - (set 'never-found-target-thread t)))))) + (when (eq status 'exit) + (if notmuch-search-process-filter-data + (insert (concat "Error: Unexpected output from notmuch search:\n" notmuch-search-process-filter-data))) + (insert "End of search results.") + (unless (= exit-status 0) + (insert (format " (process returned %d)" exit-status))) + (insert "\n") + (if (and atbob + (not (string= notmuch-search-target-thread "found"))) + (set 'never-found-target-thread t))))) (when (and never-found-target-thread notmuch-search-target-line) (goto-char (point-min)) @@ -861,10 +860,9 @@ non-authors is found, assume that all of the authors match." (put-text-property beg (point) 'notmuch-search-thread-id thread-id) (put-text-property beg (point) 'notmuch-search-authors authors) (put-text-property beg (point) 'notmuch-search-subject subject) - (if (string= thread-id notmuch-search-target-thread) - (progn - (set 'found-target beg) - (set 'notmuch-search-target-thread "found")))) + (when (string= thread-id notmuch-search-target-thread) + (set 'found-target beg) + (set 'notmuch-search-target-thread "found"))) (set 'line (match-end 0))) (set 'more nil) (while (and (< line (length string)) (= (elt string line) ?\n)) -- cgit v1.2.3 From 3f2050ac221a4c940c12442f156f12fff11600c6 Mon Sep 17 00:00:00 2001 From: Aaron Ecay Date: Fri, 3 Feb 2012 11:24:07 +0100 Subject: test: add tests for quoting of MML tags in replies The test is broken at this time; the next commit will introduce a fix. Edited-by: Pieter Praet : Rebased to release branch, moved expected output into the actual test, and fixed "Fcc:" line. --- test/emacs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/emacs b/test/emacs index f36718e..db8e4ad 100755 --- a/test/emacs +++ b/test/emacs @@ -273,6 +273,27 @@ On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite w EOF test_expect_equal_file OUTPUT EXPECTED +test_begin_subtest "Quote MML tags in reply" +test_subtest_known_broken +message_id='test-emacs-mml-quoting@message.id' +add_message [id]="$message_id" \ + "[subject]='$test_subtest_name'" \ + '[body]="<#part disposition=inline>"' +test_emacs "(notmuch-show \"id:$message_id\") + (notmuch-show-reply) + (test-output)" +cat <EXPECTED +From: Notmuch Test Suite +To: +Subject: Re: Quote MML tags in reply +In-Reply-To: +Fcc: ${MAIL_DIR}/sent +--text follows this line-- +On Tue, 05 Jan 2001 15:43:57 -0000, Notmuch Test Suite wrote: +> <#!part disposition=inline> +EOF +test_expect_equal_file OUTPUT EXPECTED + test_begin_subtest "Save attachment from within emacs using notmuch-show-save-attachments" # save as archive to test that Emacs does not re-compress .gz test_emacs '(let ((standard-input "\"attachment1.gz\"")) -- cgit v1.2.3 From ae438ccd8c77831158c7c30f19710d798ee4a6b4 Mon Sep 17 00:00:00 2001 From: Aaron Ecay Date: Fri, 3 Feb 2012 11:24:08 +0100 Subject: emacs: quote MML tags in replies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emacs message-mode uses certain text strings to indicate how to attach files to outgoing mail. If these are present in the text of an email, and a user is tricked into replying to the message, the user’s files could be exposed. Edited-by: Pieter Praet : Rebased to release branch. --- NEWS | 11 +++++++++++ emacs/notmuch-mua.el | 7 ++++++- test/emacs | 1 - 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 3d2c2a8..a089e67 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,17 @@ Fix error handling in python bindings. exceptions to indicate the error condition. Any subsequent calls into libnotmuch caused segmentation faults. +Quote MML tags in replies + + MML tags are text codes that Emacs uses to indicate attachments + (among other things) in messages being composed. The Emacs + interface did not quote MML tags in the quoted text of a reply. + User could be tricked into replying to a maliciously formatted + message and not editing out the MML tags from the quoted text. This + could lead to files from the user's machine being attached to the + outgoing message. The Emacs interface now quotes these tags in + reply text, so that they do not effect outgoing messages. + Notmuch 0.11 (2012-01-13) ========================= diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 7114e48..3e93d7c 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -111,7 +111,12 @@ list." (insert body)) (set-buffer-modified-p nil) - (message-goto-body)) + (message-goto-body) + ;; Original message may contain (malicious) MML tags. We must + ;; properly quote them in the reply. Note that using `point-max' + ;; instead of `mark' here is wrong. The buffer may include user's + ;; signature which should not be MML-quoted. + (mml-quote-region (point) (point-max))) (defun notmuch-mua-forward-message () (message-forward) diff --git a/test/emacs b/test/emacs index db8e4ad..2d066ed 100755 --- a/test/emacs +++ b/test/emacs @@ -274,7 +274,6 @@ EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Quote MML tags in reply" -test_subtest_known_broken message_id='test-emacs-mml-quoting@message.id' add_message [id]="$message_id" \ "[subject]='$test_subtest_name'" \ -- cgit v1.2.3 From 3f44da70b1dab028301641f7cfe5da6e420d60d9 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 3 Feb 2012 08:30:30 -0400 Subject: NEWS: set release date for 0.11.1 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index a089e67..e57e970 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Notmuch 0.11.1 (2012-mm-dd) +Notmuch 0.11.1 (2012-02-03) =========================== Bug-fix release. -- cgit v1.2.3 From c10b780b44128983c56253e46fa2e5f8553f2e0e Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 3 Feb 2012 08:32:49 -0400 Subject: version: bump to 0.11.1 also semi-automatically update man page and python bindings versions. --- bindings/python/notmuch/version.py | 2 +- notmuch.1 | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 59c396f..ed40e7f 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,2 +1,2 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.11' +__VERSION__ = '0.11.1' diff --git a/notmuch.1 b/notmuch.1 index a5828bc..7ab2947 100644 --- a/notmuch.1 +++ b/notmuch.1 @@ -16,7 +16,7 @@ .\" along with this program. If not, see http://www.gnu.org/licenses/ . .\" .\" Author: Carl Worth -.TH NOTMUCH 1 2012-01-13 "Notmuch 0.11" +.TH NOTMUCH 1 2012-02-03 "Notmuch 0.11.1" .SH NAME notmuch \- thread-based email index, search, and tagging .SH SYNOPSIS diff --git a/version b/version index 51176c7..af88ba8 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.11 +0.11.1 -- cgit v1.2.3 From f38bc44653ad910abb95add6b09321da11f50581 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 3 Feb 2012 08:36:58 -0400 Subject: debian: changelog for 0.11.1 mention the two bugfixes --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 26fff87..fad531a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +notmuch (0.11.1-1) unstable; urgency=low + + * Upstream bugfix release + - Fix error handling bug in python bindings + - Fix vulnerability in emacs reply handling + + -- David Bremner Fri, 03 Feb 2012 08:35:41 -0400 + notmuch (0.11-1) unstable; urgency=low * New upstream release. -- cgit v1.2.3 From b44b344595acdfe2a4be9bfb6f3421423c497c97 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Fri, 3 Feb 2012 13:12:25 +0100 Subject: configure: update explicit check for glib : >= 2.22 As of commit b3caef1f, we're using g_array_unref() in 'lib/query.cc', which was only introduced in glib 2.22, so update the dependency. Thanks to datapipe@gmail.com for reporting this [1]. Also see commit b88e6abc. [1] id:"alpine.DEB.2.02.1201132130220.21970@ltspubuntu4.int.smq.datapipe.net" --- configure | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configure b/configure index e90b76f..8b85b9d 100755 --- a/configure +++ b/configure @@ -289,10 +289,10 @@ if [ "$have_gmime" = "0" ]; then fi # GMime already depends on Glib >= 2.12, but we use at least one Glib -# function that only exists as of 2.14, (g_hash_table_get_keys) -printf "Checking for Glib development files (>= 2.14)... " +# function that only exists as of 2.22, (g_array_unref) +printf "Checking for Glib development files (>= 2.22)... " have_glib=0 -if pkg-config --exists 'glib-2.0 >= 2.14'; then +if pkg-config --exists 'glib-2.0 >= 2.22'; then printf "Yes.\n" have_glib=1 glib_cflags=$(pkg-config --cflags glib-2.0) @@ -416,7 +416,7 @@ EOF echo " http://spruce.sourceforge.net/gmime/" fi if [ $have_glib -eq 0 ]; then - echo " Glib library >= 2.14 (including development files such as headers)" + echo " Glib library >= 2.22 (including development files such as headers)" echo " http://ftp.gnome.org/pub/gnome/sources/glib/" fi if [ $have_talloc -eq 0 ]; then -- cgit v1.2.3 From 6dec2af55bb393e338586a2a399a659e54240a1f Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sat, 28 Jan 2012 08:47:39 +0400 Subject: test: remove ".sh" extension from the recently added Emacs tests All test files, except for the recently added Emacs tests, do not have ".sh" extension. So remove it from the new test files for consistency. --- test/emacs-address-cleaning | 18 ++++++++++++++++++ test/emacs-address-cleaning.sh | 18 ------------------ test/emacs-test-functions | 9 +++++++++ test/emacs-test-functions.sh | 9 --------- test/notmuch-test | 4 ++-- 5 files changed, 29 insertions(+), 29 deletions(-) create mode 100755 test/emacs-address-cleaning delete mode 100755 test/emacs-address-cleaning.sh create mode 100755 test/emacs-test-functions delete mode 100755 test/emacs-test-functions.sh diff --git a/test/emacs-address-cleaning b/test/emacs-address-cleaning new file mode 100755 index 0000000..51018fe --- /dev/null +++ b/test/emacs-address-cleaning @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +test_description="emacs address cleaning" +. test-lib.sh + +test_begin_subtest "notmuch-test-address-clean part 1" +test_emacs_expect_t \ + '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-1)' + +test_begin_subtest "notmuch-test-address-clean part 2" +test_emacs_expect_t \ + '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-2)' + +test_begin_subtest "notmuch-test-address-clean part 3" +test_emacs_expect_t \ + '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-3)' + +test_done diff --git a/test/emacs-address-cleaning.sh b/test/emacs-address-cleaning.sh deleted file mode 100755 index 51018fe..0000000 --- a/test/emacs-address-cleaning.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -test_description="emacs address cleaning" -. test-lib.sh - -test_begin_subtest "notmuch-test-address-clean part 1" -test_emacs_expect_t \ - '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-1)' - -test_begin_subtest "notmuch-test-address-clean part 2" -test_emacs_expect_t \ - '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-2)' - -test_begin_subtest "notmuch-test-address-clean part 3" -test_emacs_expect_t \ - '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-3)' - -test_done diff --git a/test/emacs-test-functions b/test/emacs-test-functions new file mode 100755 index 0000000..0e1f9fc --- /dev/null +++ b/test/emacs-test-functions @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +test_description="emacs test function sanity" +. test-lib.sh + +test_begin_subtest "emacs test function sanity" +test_emacs_expect_t 't' + +test_done diff --git a/test/emacs-test-functions.sh b/test/emacs-test-functions.sh deleted file mode 100755 index 0e1f9fc..0000000 --- a/test/emacs-test-functions.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -test_description="emacs test function sanity" -. test-lib.sh - -test_begin_subtest "emacs test function sanity" -test_emacs_expect_t 't' - -test_done diff --git a/test/notmuch-test b/test/notmuch-test index 3f1740c..ced6b47 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -52,8 +52,8 @@ TESTS=" python hooks argument-parsing - emacs-test-functions.sh - emacs-address-cleaning.sh + emacs-test-functions + emacs-address-cleaning " TESTS=${NOTMUCH_TESTS:=$TESTS} -- cgit v1.2.3 From a8ee1c75c348f41d88398fc394812e66e8e8b72e Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 29 Jan 2012 00:50:08 -0500 Subject: lib: Don't delete uninitialized pointers In the error-handling paths of notmuch_database_open, we call notmuch_database_close, which "delete"s several objects referenced by the notmuch_database_t object. However, some of these pointers may be uninitialized, resulting in undefined behavior. Hence, allocate the notmuch_database_t with talloc_zero to make sure these pointers are NULL so that "delete"ing them is harmless. --- lib/database.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/database.cc b/lib/database.cc index 8103bd9..a6d15a1 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -617,7 +617,7 @@ notmuch_database_open (const char *path, initialized = 1; } - notmuch = talloc (NULL, notmuch_database_t); + notmuch = talloc_zero (NULL, notmuch_database_t); notmuch->exception_reported = FALSE; notmuch->path = talloc_strdup (notmuch, path); -- cgit v1.2.3 From 6c0adab23eadb81bb1254f4b82175902df11bb3f Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 29 Jan 2012 00:50:09 -0500 Subject: lib: Release resources if notmuch_database_open fails Previously, if a Xapian exception occurred in notmuch_database_open, we failed to clean up the allocated notmuch_database_t object. --- lib/database.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/database.cc b/lib/database.cc index a6d15a1..94022d7 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -703,6 +703,7 @@ notmuch_database_open (const char *path, } catch (const Xapian::Error &error) { fprintf (stderr, "A Xapian exception occurred opening database: %s\n", error.get_msg().c_str()); + notmuch_database_close (notmuch); notmuch = NULL; } -- cgit v1.2.3 From c32116d04807eec475d7e19bb6723c35bc399059 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 29 Jan 2012 00:50:10 -0500 Subject: lib: Use talloc to simplify cleanup in notmuch_database_open Previously, we manually "free"d various pointers in notmuch_database_open. Use a local talloc context instead to simplify cleanup and eliminate various NULL pointer initializations and conditionals. --- lib/database.cc | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 94022d7..c928d02 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -582,15 +582,15 @@ notmuch_database_t * notmuch_database_open (const char *path, notmuch_database_mode_t mode) { + void *local = talloc_new (NULL); notmuch_database_t *notmuch = NULL; - char *notmuch_path = NULL, *xapian_path = NULL; + char *notmuch_path, *xapian_path; struct stat st; int err; unsigned int i, version; static int initialized = 0; - if (asprintf (¬much_path, "%s/%s", path, ".notmuch") == -1) { - notmuch_path = NULL; + if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) { fprintf (stderr, "Out of memory\n"); goto DONE; } @@ -602,8 +602,7 @@ notmuch_database_open (const char *path, goto DONE; } - if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) { - xapian_path = NULL; + if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) { fprintf (stderr, "Out of memory\n"); goto DONE; } @@ -708,10 +707,7 @@ notmuch_database_open (const char *path, } DONE: - if (notmuch_path) - free (notmuch_path); - if (xapian_path) - free (xapian_path); + talloc_free (local); return notmuch; } -- cgit v1.2.3 From e516a712bb79e523629012ee6a8325f5166b4ee5 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Mon, 30 Jan 2012 10:16:00 +0000 Subject: emacs: Stop the `truncate-string-to-width' madness. There's no need to call `truncate-string-to-width' twice in this code path. --- emacs/notmuch.el | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 5fa239a..05c2ff7 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -469,18 +469,16 @@ Complete list of currently available key bindings: (let ((thread-id (notmuch-search-find-thread-id)) (subject (notmuch-search-find-subject))) (if (> (length thread-id) 0) - (notmuch-show thread-id - (current-buffer) - notmuch-search-query-string - ;; name the buffer based on notmuch-search-find-subject - (if (string-match "^[ \t]*$" subject) - "[No Subject]" - (truncate-string-to-width - (concat "*" - (truncate-string-to-width subject 32 nil nil t) - "*") - 32 nil nil t)) - crypto-switch) + (progn + (if (string-match "^[ \t]*$" subject) + (setq subject "[No Subject]")) + + (notmuch-show thread-id + (current-buffer) + notmuch-search-query-string + ;; Name the buffer based on the subject. + (concat "*" (truncate-string-to-width subject 30 nil nil t) "*") + crypto-switch)) (message "End of search results.")))) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender) -- cgit v1.2.3 From 6bd3d8af5431542f352f084b6366e88b98b019a1 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Mon, 30 Jan 2012 10:16:01 +0000 Subject: emacs: Prefer '[No Subject]' to blank subjects. --- emacs/notmuch-lib.el | 9 +++++++++ emacs/notmuch-print.el | 8 ++++++-- emacs/notmuch-show.el | 5 ++++- emacs/notmuch.el | 21 +++++++++------------ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index c906ca7..d315f76 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -133,6 +133,15 @@ the user hasn't set this variable with the old or new value." (interactive) (kill-buffer (current-buffer))) +(defun notmuch-prettify-subject (subject) + ;; This function is used by `notmuch-search-process-filter' which + ;; requires that we not disrupt its' matching state. + (save-match-data + (if (and subject + (string-match "^[ \t]*$" subject)) + "[No Subject]" + subject))) + ;; (defun notmuch-common-do-stash (text) diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el index 880f96d..6653d97 100644 --- a/emacs/notmuch-print.el +++ b/emacs/notmuch-print.el @@ -19,6 +19,8 @@ ;; ;; Authors: David Edmondson +(require 'notmuch-lib) + (declare-function notmuch-show-get-prop "notmuch-show" (prop &optional props)) (defcustom notmuch-print-mechanism 'notmuch-print-lpr @@ -58,14 +60,16 @@ Optional OUTPUT allows passing a list of flags to muttprint." (defun notmuch-print-ps-print (msg) "Print a message buffer using the ps-print package." - (let ((subject (plist-get (notmuch-show-get-prop :headers msg) :Subject))) + (let ((subject (notmuch-prettify-subject + (plist-get (notmuch-show-get-prop :headers msg) :Subject)))) (rename-buffer subject t) (ps-print-buffer))) (defun notmuch-print-ps-print/evince (msg) "Preview a message buffer using ps-print and evince." (let ((ps-file (make-temp-file "notmuch")) - (subject (plist-get (notmuch-show-get-prop :headers msg) :Subject))) + (subject (notmuch-prettify-subject + (plist-get (notmuch-show-get-prop :headers msg) :Subject)))) (rename-buffer subject t) (ps-print-buffer ps-file) (notmuch-print-run-evince ps-file))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 0a945ea..3a1a8c8 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1018,7 +1018,7 @@ buffer." (notmuch-show-next-open-message)) ;; Set the header line to the subject of the first open message. - (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-subject))) + (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-pretty-subject))) (notmuch-show-mark-read))) @@ -1250,6 +1250,9 @@ Some useful entries are: (defun notmuch-show-get-depth () (notmuch-show-get-prop :depth)) +(defun notmuch-show-get-pretty-subject () + (notmuch-prettify-subject (notmuch-show-get-subject))) + (defun notmuch-show-set-tags (tags) "Set the tags of the current message." (notmuch-show-set-prop :tags tags) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 05c2ff7..cd04ffd 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -467,18 +467,14 @@ Complete list of currently available key bindings: "Display the currently selected thread." (interactive "P") (let ((thread-id (notmuch-search-find-thread-id)) - (subject (notmuch-search-find-subject))) + (subject (notmuch-prettify-subject (notmuch-search-find-subject)))) (if (> (length thread-id) 0) - (progn - (if (string-match "^[ \t]*$" subject) - (setq subject "[No Subject]")) - - (notmuch-show thread-id - (current-buffer) - notmuch-search-query-string - ;; Name the buffer based on the subject. - (concat "*" (truncate-string-to-width subject 30 nil nil t) "*") - crypto-switch)) + (notmuch-show thread-id + (current-buffer) + notmuch-search-query-string + ;; Name the buffer based on the subject. + (concat "*" (truncate-string-to-width subject 30 nil nil t) "*") + crypto-switch) (message "End of search results.")))) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender) @@ -853,7 +849,8 @@ non-authors is found, assume that all of the authors match." (if (/= (match-beginning 1) line) (insert (concat "Error: Unexpected output from notmuch search:\n" (substring string line (match-beginning 1)) "\n"))) (let ((beg (point))) - (notmuch-search-show-result date count authors subject tags) + (notmuch-search-show-result date count authors + (notmuch-prettify-subject subject) tags) (notmuch-search-color-line beg (point) tag-list) (put-text-property beg (point) 'notmuch-search-thread-id thread-id) (put-text-property beg (point) 'notmuch-search-authors authors) -- cgit v1.2.3 From 32d7b3aabd4cdba0fcf80e95a701bb64d3bf4980 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Mon, 30 Jan 2012 14:59:54 +0000 Subject: emacs: More address cleaning. Remove outer single-quotes from the mailbox part. Allow for multiple sets of nested single and double quotes. Add more tests. --- emacs/notmuch-show.el | 24 +++++++++++++++++------- test/emacs-address-cleaning.el | 8 ++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 3a1a8c8..26cd221 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -316,15 +316,25 @@ unchanged ADDRESS if parsing fails." (t (setq p-address address))) - ;; Remove elements of the mailbox part that are not relevant for - ;; display, even if they are required during transport. (when p-name - ;; Outer double quotes. - (when (string-match "^\"\\(.*\\)\"$" p-name) - (setq p-name (match-string 1 p-name))) - + ;; Remove elements of the mailbox part that are not relevant for + ;; display, even if they are required during transport: + ;; ;; Backslashes. - (setq p-name (replace-regexp-in-string "\\\\" "" p-name))) + (setq p-name (replace-regexp-in-string "\\\\" "" p-name)) + + ;; Outer single and double quotes, which might be nested. + (loop + with start-of-loop + do (setq start-of-loop p-name) + + when (string-match "^\"\\(.*\\)\"$" p-name) + do (setq p-name (match-string 1 p-name)) + + when (string-match "^'\\(.*\\)'$" p-name) + do (setq p-name (match-string 1 p-name)) + + until (string= start-of-loop p-name))) ;; If the address is 'foo@bar.com ' then show just ;; 'foo@bar.com'. diff --git a/test/emacs-address-cleaning.el b/test/emacs-address-cleaning.el index 3b0b109..8423245 100644 --- a/test/emacs-address-cleaning.el +++ b/test/emacs-address-cleaning.el @@ -21,11 +21,19 @@ "foo (at home) " "foo [at home] " "Foo Bar" + "'Foo Bar' " + "\"'Foo Bar'\" " + "'\"Foo Bar\"' " + "'\"'Foo Bar'\"' " "Fred Dibna \\[extraordinaire\\] ")) (expected '("ДБ " "foo (at home) " "foo [at home] " "Foo Bar" + "Foo Bar " + "Foo Bar " + "Foo Bar " + "Foo Bar " "Fred Dibna [extraordinaire] ")) (output (mapcar #'notmuch-show-clean-address input))) (notmuch-test-expect-equal output expected))) -- cgit v1.2.3 From d8d73878815f3038c1a4005e43d35e6b7acf722f Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Mon, 30 Jan 2012 16:52:20 +0000 Subject: emacs: Move the blank line from the bottom of the headers to the top of the body. The blank line doesn't really change position, but is now considered to be part of the body rather than part of the headers. This means that it is visible when the body is visible rather than when the headers are visible. --- emacs/notmuch-show.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 26cd221..7469e2e 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -865,8 +865,6 @@ current buffer, if possible." ;; compatible with the existing implementation. This just sets it ;; to after the first header. (notmuch-show-insert-headers headers) - ;; Headers should include a blank line (backwards compatibility). - (insert "\n") (save-excursion (goto-char content-start) ;; If the subject of this message is the same as that of the @@ -881,6 +879,8 @@ current buffer, if possible." (setq notmuch-show-previous-subject bare-subject) (setq body-start (point-marker)) + ;; A blank line between the headers and the body. + (insert "\n") (notmuch-show-insert-body msg (plist-get msg :body) depth) ;; Ensure that the body ends with a newline. (unless (bolp) -- cgit v1.2.3 From 69adeb52b1c095f570f590e8a8db7f219b875b35 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sat, 4 Feb 2012 11:36:36 +0400 Subject: test: add test for hiding Original Message region at beginning of a message The test is currently broken and will be fixed by a subsequent patch. The patch adds a new file for tests of Emacs notmuch-show view. Based on patch by David Edmondson [1]. [1] id:"1327562380-12894-4-git-send-email-dme@dme.org" --- test/emacs-show | 28 ++++++++++++++++++++++++++++ test/notmuch-test | 1 + 2 files changed, 29 insertions(+) create mode 100755 test/emacs-show diff --git a/test/emacs-show b/test/emacs-show new file mode 100755 index 0000000..9800575 --- /dev/null +++ b/test/emacs-show @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +test_description="Testing emacs notmuch-show view" +. test-lib.sh + +test_begin_subtest "Hiding Original Message region at beginning of a message" +test_subtest_known_broken +message_id='OriginalMessageHiding.1@notmuchmail.org' +add_message \ + [id]="$message_id" \ + '[subject]="Hiding Original Message region at beginning of a message"' \ + '[body]="-----Original Message----- +Text here."' + +cat <EXPECTED +Notmuch Test Suite (2001-01-05) (inbox) +Subject: Hiding Original Message region at beginning of a message +To: Notmuch Test Suite +Date: Fri, 05 Jan 2001 15:43:57 +0000 + +[ 2-line hidden original message. Click/Enter to show. ] +EOF + +test_emacs "(notmuch-show \"id:$message_id\") + (test-visible-output)" +test_expect_equal_file OUTPUT EXPECTED + +test_done diff --git a/test/notmuch-test b/test/notmuch-test index ced6b47..e14d34e 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -54,6 +54,7 @@ TESTS=" argument-parsing emacs-test-functions emacs-address-cleaning + emacs-show " TESTS=${NOTMUCH_TESTS:=$TESTS} -- cgit v1.2.3 From 8281430558c1b2eafc847620cd84f5ce40bf6741 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sat, 4 Feb 2012 11:36:37 +0400 Subject: emacs: fix `notmuch-wash-region-to-button' to work at beginning of buffer `Notmuch-wash-region-to-button' is the function that creates hidden regions with buttons for signatures, citations and original messages. Before the change, it did not work correctly if the to-be-hidden region started at the beginning of a message: the visibility toggle button was hidden as well. The patch fixes this. There are two parts in the fix: * Use `insert-before-markers' instead of `insert' for creating the button, so that it does not get added to the hidden overlay. * Stop using PREFIX argument for adding a newline before the button. The newline should not be added before a button at the beginning of buffer. The corresponding test is fixed now. --- emacs/notmuch-wash.el | 24 ++++++++++++++---------- test/emacs-show | 1 - 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el index 67143e5..56981d0 100644 --- a/emacs/notmuch-wash.el +++ b/emacs/notmuch-wash.el @@ -136,12 +136,13 @@ collapse the remaining lines into a button.") (lines-count (count-lines (overlay-start overlay) (overlay-end overlay)))) (format label-format lines-count))) -(defun notmuch-wash-region-to-button (msg beg end type prefix) +(defun notmuch-wash-region-to-button (msg beg end type &optional prefix) "Auxiliary function to do the actual making of overlays and buttons BEG and END are buffer locations. TYPE should a string, either -\"citation\" or \"signature\". PREFIX is some arbitrary text to -insert before the button, probably for indentation." +\"citation\" or \"signature\". Optional PREFIX is some arbitrary +text to insert before the button, probably for indentation. Note +that PREFIX should not include a newline." ;; This uses some slightly tricky conversions between strings and ;; symbols because of the way the button code works. Note that @@ -160,12 +161,15 @@ insert before the button, probably for indentation." (overlay-put overlay 'type type) (goto-char (1+ end)) (save-excursion - (goto-char (1- beg)) - (insert prefix) - (insert-button (notmuch-wash-button-label overlay) + (goto-char beg) + (if prefix + (insert-before-markers prefix)) + (let ((button-beg (point))) + (insert-before-markers (notmuch-wash-button-label overlay) "\n") + (make-button button-beg (1- (point)) 'invisibility-spec invis-spec 'overlay overlay - :type button-type)))) + :type button-type))))) (defun notmuch-wash-excerpt-citations (msg depth) "Excerpt citations and up to one signature." @@ -177,7 +181,7 @@ insert before the button, probably for indentation." (msg-end (point-max)) (msg-lines (count-lines msg-start msg-end))) (notmuch-wash-region-to-button - msg msg-start msg-end "original" "\n"))) + msg msg-start msg-end "original"))) (while (and (< (point) (point-max)) (re-search-forward notmuch-wash-citation-regexp nil t)) (let* ((cite-start (match-beginning 0)) @@ -194,7 +198,7 @@ insert before the button, probably for indentation." (forward-line (- notmuch-wash-citation-lines-suffix)) (notmuch-wash-region-to-button msg hidden-start (point-marker) - "citation" "\n"))))) + "citation"))))) (if (and (not (eobp)) (re-search-forward notmuch-wash-signature-regexp nil t)) (let* ((sig-start (match-beginning 0)) @@ -208,7 +212,7 @@ insert before the button, probably for indentation." (overlay-put (make-overlay sig-start-marker sig-end-marker) 'face 'message-cited-text) (notmuch-wash-region-to-button msg sig-start-marker sig-end-marker - "signature" "\n")))))) + "signature")))))) ;; diff --git a/test/emacs-show b/test/emacs-show index 9800575..5700d2e 100755 --- a/test/emacs-show +++ b/test/emacs-show @@ -4,7 +4,6 @@ test_description="Testing emacs notmuch-show view" . test-lib.sh test_begin_subtest "Hiding Original Message region at beginning of a message" -test_subtest_known_broken message_id='OriginalMessageHiding.1@notmuchmail.org' add_message \ [id]="$message_id" \ -- cgit v1.2.3 From 57702cc43017cfd5c5ad65a986ef962f5e40cd59 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Feb 2012 13:37:33 -0500 Subject: test: Fix up date in MML quoting tests. based on id:"1328264649-27346-3-git-send-email-pieter@praet.org" Commit 66ecd9063 made dates "real", but it hasn't hit release yet. --- test/emacs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/emacs b/test/emacs index f4a5c81..256a738 100755 --- a/test/emacs +++ b/test/emacs @@ -288,7 +288,7 @@ Subject: Re: Quote MML tags in reply In-Reply-To: Fcc: ${MAIL_DIR}/sent --text follows this line-- -On Tue, 05 Jan 2001 15:43:57 -0000, Notmuch Test Suite wrote: +On Fri, 05 Jan 2001 15:43:57 +0000, Notmuch Test Suite wrote: > <#!part disposition=inline> EOF test_expect_equal_file OUTPUT EXPECTED -- cgit v1.2.3 From 2c6710e3ba22f5af6e5813dad8bee732e6c5d02c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 4 Feb 2012 13:40:24 -0500 Subject: emacs: use mark instead of point-max in MML quoting. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As Aaron explains in id:"m2vco72tf3.fsf@wal122.wireless-pennnet.upenn.edu" Using point-max would include the signature in the quoting as well. It would probably be fairly odd to want to put an MML tag in one’s signature, but that doesn’t mean that we should break that usage. We had to use point-max in the 0.11.1 bug-fix release, because the mark functionality was added post 0.11. --- emacs/notmuch-mua.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index c07b67b..4be7c13 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -121,7 +121,7 @@ list." ;; properly quote them in the reply. Note that using `point-max' ;; instead of `mark' here is wrong. The buffer may include user's ;; signature which should not be MML-quoted. - (mml-quote-region (point) (point-max))) + (mml-quote-region (point) (mark))) (defun notmuch-mua-forward-message () (message-forward) -- cgit v1.2.3 From cc3756aabed5d6e50a481a5d38a5859754206be2 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sat, 28 Jan 2012 12:02:33 +0400 Subject: tag: remove unused attribute from notmuch_tag_command() arguments Argc and argv arguments are used in notmuch_tag_command() function. So unused attribute is not appropriate for them. --- notmuch-tag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notmuch-tag.c b/notmuch-tag.c index 44fd61f..36b9b09 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -111,7 +111,7 @@ _optimize_tag_query (void *ctx, const char *orig_query_string, char *argv[], } int -notmuch_tag_command (void *ctx, unused (int argc), unused (char *argv[])) +notmuch_tag_command (void *ctx, int argc, char *argv[]) { int *add_tags, *remove_tags; int add_tags_count = 0; -- cgit v1.2.3 From 0adf05f57848d1fa190d2a6d33eeba2c0d992b51 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:42 +0400 Subject: emacs: move tag format validation to `notmuch-tag' function Before the change, tag format validation was done in `notmuch-search-operate-all' function only. The patch moves it down to `notmuch-tag', so that all users of that function get input validation. --- emacs/notmuch.el | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index cd04ffd..19206db 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -516,6 +516,12 @@ Note: Other code should always use this function alter tags of messages instead of running (notmuch-call-notmuch-process \"tag\" ..) directly, so that hooks specified in notmuch-before-tag-hook and notmuch-after-tag-hook will be run." + ;; Perform some validation + (when (null tags) (error "No tags given")) + (mapc (lambda (tag) + (unless (string-match-p "^[-+][-+_.[:word:]]+$" tag) + (error "Tag must be of the form `+this_tag' or `-that_tag'"))) + tags) (run-hooks 'notmuch-before-tag-hook) (apply 'notmuch-call-notmuch-process (append (list "tag") tags (list "--" query))) @@ -883,12 +889,6 @@ characters as well as `_.+-'. (interactive (notmuch-select-tags-with-completion "Operations (+add -drop): notmuch tag " '("+" "-"))) - ;; Perform some validation - (when (null actions) (error "No operations given")) - (mapc (lambda (action) - (unless (string-match-p "^[-+][-+_.[:word:]]+$" action) - (error "Action must be of the form `+this_tag' or `-that_tag'"))) - actions) (apply 'notmuch-tag notmuch-search-query-string actions)) (defun notmuch-search-buffer-title (query) -- cgit v1.2.3 From b3eafaf72522a3145cc4f14ec453eff408e6319e Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:43 +0400 Subject: emacs: remove text properties from `notmuch-search-get-tags' result --- emacs/notmuch.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 19206db..5980fea 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -571,7 +571,7 @@ the messages that were tagged" (let ((beg (+ (point) 1))) (re-search-forward ")") (let ((end (- (point) 1))) - (split-string (buffer-substring beg end)))))) + (split-string (buffer-substring-no-properties beg end)))))) (defun notmuch-search-get-tags-region (beg end) (save-excursion -- cgit v1.2.3 From 2beaefa2ec54b772d29b87c2f5649d0dcf2e5922 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:44 +0400 Subject: emacs: make "+" and "-" tagging operations in notmuch-search more flexible Before the change, "+" and "-" tagging operations in notmuch-search view accepted only a single tag. The patch makes them use the recently added `notmuch-read-tag-changes' function (renamed `notmuch-select-tags-with-completion'), which allows to enter multiple tags with "+" and "-" prefixes. So after the change, "+" and "-" bindings in notmuch-search view allow to both add and remove multiple tags. The only difference between "+" and "-" is the minibuffer initial input ("+" and "-" respectively). --- emacs/notmuch.el | 163 +++++++++++++++++++++++++++---------------------------- 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 5980fea..1b472dd 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -76,38 +76,56 @@ For example: (defvar notmuch-query-history nil "Variable to store minibuffer history for notmuch queries") -(defun notmuch-tag-completions (&optional prefixes search-terms) - (let ((tag-list - (split-string - (with-output-to-string - (with-current-buffer standard-output - (apply 'call-process notmuch-command nil t - nil "search-tags" search-terms))) - "\n+" t))) - (if (null prefixes) - tag-list - (apply #'append - (mapcar (lambda (tag) - (mapcar (lambda (prefix) - (concat prefix tag)) prefixes)) - tag-list))))) +(defun notmuch-tag-completions (&optional search-terms) + (split-string + (with-output-to-string + (with-current-buffer standard-output + (apply 'call-process notmuch-command nil t + nil "search-tags" search-terms))) + "\n+" t)) (defun notmuch-select-tag-with-completion (prompt &rest search-terms) - (let ((tag-list (notmuch-tag-completions nil search-terms))) + (let ((tag-list (notmuch-tag-completions search-terms))) (completing-read prompt tag-list))) -(defun notmuch-select-tags-with-completion (prompt &optional prefixes &rest search-terms) - (let ((tag-list (notmuch-tag-completions prefixes search-terms)) - (crm-separator " ") - ;; By default, space is bound to "complete word" function. - ;; Re-bind it to insert a space instead. Note that - ;; still does the completion. - (crm-local-completion-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map crm-local-completion-map) - (define-key map " " 'self-insert-command) - map))) - (delete "" (completing-read-multiple prompt tag-list)))) +(defun notmuch-read-tag-changes (&optional initial-input &rest search-terms) + (let* ((all-tag-list (notmuch-tag-completions)) + (add-tag-list (mapcar (apply-partially 'concat "+") all-tag-list)) + (remove-tag-list (mapcar (apply-partially 'concat "-") + (if (null search-terms) + all-tag-list + (notmuch-tag-completions search-terms)))) + (tag-list (append add-tag-list remove-tag-list)) + (crm-separator " ") + ;; By default, space is bound to "complete word" function. + ;; Re-bind it to insert a space instead. Note that + ;; still does the completion. + (crm-local-completion-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map crm-local-completion-map) + (define-key map " " 'self-insert-command) + map))) + (delete "" (completing-read-multiple "Tags (+add -drop): " + tag-list nil nil initial-input)))) + +(defun notmuch-update-tags (tags tag-changes) + "Return a copy of TAGS with additions and removals from TAG-CHANGES. + +TAG-CHANGES must be a list of tags names, each prefixed with +either a \"+\" to indicate the tag should be added to TAGS if not +present or a \"-\" to indicate that the tag should be removed +from TAGS if present." + (let ((result-tags (copy-sequence tags))) + (dolist (tag-change tag-changes) + (let ((op (string-to-char tag-change)) + (tag (unless (string= tag-change "") (substring tag-change 1)))) + (case op + (?+ (unless (member tag result-tags) + (push tag result-tags))) + (?- (setq result-tags (delete tag result-tags))) + (otherwise + (error "Changed tag must be of the form `+this_tag' or `-that_tag'"))))) + (sort result-tags 'string<))) (defun notmuch-foreach-mime-part (function mm-handle) (cond ((stringp (car mm-handle)) @@ -447,6 +465,10 @@ Complete list of currently available key bindings: "Return a list of threads for the current region" (notmuch-search-properties-in-region 'notmuch-search-thread-id beg end)) +(defun notmuch-search-find-thread-id-region-search (beg end) + "Return a search string for threads for the current region" + (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")) + (defun notmuch-search-find-authors () "Return the authors for the current thread" (get-text-property (point) 'notmuch-search-authors)) @@ -584,74 +606,53 @@ the messages that were tagged" (forward-line 1)) output))) -(defun notmuch-search-add-tag-thread (tag) - (notmuch-search-add-tag-region tag (point) (point))) +(defun notmuch-search-tag-thread (&rest tags) + "Change tags for the currently selected thread. -(defun notmuch-search-add-tag-region (tag beg end) - (let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or "))) - (notmuch-tag search-id-string (concat "+" tag)) - (save-excursion - (let ((last-line (line-number-at-pos end)) - (max-line (- (line-number-at-pos (point-max)) 2))) - (goto-char beg) - (while (<= (line-number-at-pos) (min last-line max-line)) - (notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<))) - (forward-line)))))) +See `notmuch-search-tag-region' for details." + (apply 'notmuch-search-tag-region (point) (point) tags)) -(defun notmuch-search-remove-tag-thread (tag) - (notmuch-search-remove-tag-region tag (point) (point))) +(defun notmuch-search-tag-region (beg end &rest tags) + "Change tags for threads in the given region. -(defun notmuch-search-remove-tag-region (tag beg end) - (let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or "))) - (notmuch-tag search-id-string (concat "-" tag)) +TAGS is a list of tag operations for `notmuch-tag'. The tags are +added or removed for all threads in the region from BEG to END." + (let ((search-string (notmuch-search-find-thread-id-region-search beg end))) + (apply 'notmuch-tag search-string tags) (save-excursion (let ((last-line (line-number-at-pos end)) (max-line (- (line-number-at-pos (point-max)) 2))) (goto-char beg) (while (<= (line-number-at-pos) (min last-line max-line)) - (notmuch-search-set-tags (delete tag (notmuch-search-get-tags))) + (notmuch-search-set-tags + (notmuch-update-tags (notmuch-search-get-tags) tags)) (forward-line)))))) -(defun notmuch-search-add-tag (tag) - "Add a tag to the currently selected thread or region. - -The tag is added to all messages in the currently selected thread -or threads in the current region." - (interactive - (list (notmuch-select-tag-with-completion "Tag to add: "))) - (save-excursion - (if (region-active-p) - (let* ((beg (region-beginning)) - (end (region-end))) - (notmuch-search-add-tag-region tag beg end)) - (notmuch-search-add-tag-thread tag)))) - -(defun notmuch-search-remove-tag (tag) - "Remove a tag from the currently selected thread or region. +(defun notmuch-search-tag (&optional initial-input) + "Change tags for the currently selected thread or region." + (interactive) + (let* ((beg (if (region-active-p) (region-beginning) (point))) + (end (if (region-active-p) (region-end) (point))) + (search-string (notmuch-search-find-thread-id-region-search beg end)) + (tags (notmuch-read-tag-changes initial-input search-string))) + (apply 'notmuch-search-tag-region beg end tags))) + +(defun notmuch-search-add-tag () + "Same as `notmuch-search-tag' but sets initial input to '+'." + (interactive) + (notmuch-search-tag "+")) -The tag is removed from all messages in the currently selected -thread or threads in the current region." - (interactive - (list (notmuch-select-tag-with-completion - "Tag to remove: " - (if (region-active-p) - (mapconcat 'identity - (notmuch-search-find-thread-id-region (region-beginning) (region-end)) - " ") - (notmuch-search-find-thread-id))))) - (save-excursion - (if (region-active-p) - (let* ((beg (region-beginning)) - (end (region-end))) - (notmuch-search-remove-tag-region tag beg end)) - (notmuch-search-remove-tag-thread tag)))) +(defun notmuch-search-remove-tag () + "Same as `notmuch-search-tag' but sets initial input to '-'." + (interactive) + (notmuch-search-tag "-")) (defun notmuch-search-archive-thread () "Archive the currently selected thread (remove its \"inbox\" tag). This function advances the next thread when finished." (interactive) - (notmuch-search-remove-tag-thread "inbox") + (notmuch-search-tag-thread "-inbox") (notmuch-search-next-thread)) (defvar notmuch-search-process-filter-data nil @@ -886,9 +887,7 @@ will prompt for tags to be added or removed. Tags prefixed with Each character of the tag name may consist of alphanumeric characters as well as `_.+-'. " - (interactive (notmuch-select-tags-with-completion - "Operations (+add -drop): notmuch tag " - '("+" "-"))) + (interactive (notmuch-read-tag-changes)) (apply 'notmuch-tag notmuch-search-query-string actions)) (defun notmuch-search-buffer-title (query) -- cgit v1.2.3 From 389ddf0f123259b3a2cad24be2b356dd5219c49b Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:45 +0400 Subject: emacs: make "+" and "-" tagging operations in notmuch-show more flexible Before the change, "+" and "-" tagging operations in notmuch-show view accepted only a single tag. The patch makes them use the recently added `notmuch-read-tag-changes' function, which allows to enter multiple tags with "+" and "-" prefixes. So after the change, "+" and "-" bindings in notmuch-show view allow to both add and remove multiple tags. The only difference between "+" and "-" is the minibuffer initial input ("+" and "-" respectively). --- emacs/notmuch-show.el | 73 ++++++++++++++++++--------------------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 7469e2e..48a2a60 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -38,9 +38,10 @@ (declare-function notmuch-call-notmuch-process "notmuch" (&rest args)) (declare-function notmuch-fontify-headers "notmuch" nil) -(declare-function notmuch-select-tag-with-completion "notmuch" (prompt &rest search-terms)) +(declare-function notmuch-read-tag-changes "notmuch" (&optional initial-input &rest search-terms)) (declare-function notmuch-search-next-thread "notmuch" nil) (declare-function notmuch-search-show-thread "notmuch" nil) +(declare-function notmuch-update-tags "notmuch" (current-tags tag-changes)) (defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date") "Headers that should be shown in a message, in this order. @@ -1282,7 +1283,7 @@ Some useful entries are: (defun notmuch-show-mark-read () "Mark the current message as read." - (notmuch-show-remove-tag "unread")) + (notmuch-show-tag-message "-unread")) ;; Functions for getting attributes of several messages in the current ;; thread. @@ -1495,51 +1496,32 @@ than only the current message." (message (format "Command '%s' exited abnormally with code %d" shell-command exit-code)))))))) -(defun notmuch-show-add-tags-worker (current-tags add-tags) - "Add to `current-tags' with any tags from `add-tags' not -currently present and return the result." - (let ((result-tags (copy-sequence current-tags))) - (mapc (lambda (add-tag) - (unless (member add-tag current-tags) - (setq result-tags (push add-tag result-tags)))) - add-tags) - (sort result-tags 'string<))) - -(defun notmuch-show-del-tags-worker (current-tags del-tags) - "Remove any tags in `del-tags' from `current-tags' and return -the result." - (let ((result-tags (copy-sequence current-tags))) - (mapc (lambda (del-tag) - (setq result-tags (delete del-tag result-tags))) - del-tags) - result-tags)) - -(defun notmuch-show-add-tag (&rest toadd) - "Add a tag to the current message." - (interactive - (list (notmuch-select-tag-with-completion "Tag to add: "))) +(defun notmuch-show-tag-message (&rest tag-changes) + "Change tags for the current message. +TAG-CHANGES is a list of tag operations for `notmuch-tag'." (let* ((current-tags (notmuch-show-get-tags)) - (new-tags (notmuch-show-add-tags-worker current-tags toadd))) - + (new-tags (notmuch-update-tags current-tags tag-changes))) (unless (equal current-tags new-tags) - (apply 'notmuch-tag (notmuch-show-get-message-id) - (mapcar (lambda (s) (concat "+" s)) toadd)) + (apply 'notmuch-tag (notmuch-show-get-message-id) tag-changes) (notmuch-show-set-tags new-tags)))) -(defun notmuch-show-remove-tag (&rest toremove) - "Remove a tag from the current message." - (interactive - (list (notmuch-select-tag-with-completion - "Tag to remove: " (notmuch-show-get-message-id)))) +(defun notmuch-show-tag (&optional initial-input) + "Change tags for the current message, read input from the minibuffer." + (interactive) + (let ((tag-changes (notmuch-read-tag-changes + initial-input (notmuch-show-get-message-id)))) + (apply 'notmuch-show-tag-message tag-changes))) - (let* ((current-tags (notmuch-show-get-tags)) - (new-tags (notmuch-show-del-tags-worker current-tags toremove))) +(defun notmuch-show-add-tag () + "Same as `notmuch-show-tag' but sets initial input to '+'." + (interactive) + (notmuch-show-tag "+")) - (unless (equal current-tags new-tags) - (apply 'notmuch-tag (notmuch-show-get-message-id) - (mapcar (lambda (s) (concat "-" s)) toremove)) - (notmuch-show-set-tags new-tags)))) +(defun notmuch-show-remove-tag () + "Same as `notmuch-show-tag' but sets initial input to '-'." + (interactive) + (notmuch-show-tag "-")) (defun notmuch-show-toggle-headers () "Toggle the visibility of the current message headers." @@ -1587,10 +1569,8 @@ argument, hide all of the messages." If the remove switch is given, tags will be removed instead of added." (goto-char (point-min)) - (let ((tag-function (if remove - 'notmuch-show-remove-tag - 'notmuch-show-add-tag))) - (loop do (funcall tag-function tag) + (let ((op (if remove "-" "+"))) + (loop do (notmuch-show-tag-message (concat op tag)) until (not (notmuch-show-goto-message-next))))) (defun notmuch-show-add-tag-thread (tag) @@ -1653,9 +1633,8 @@ If a prefix argument is given, the message will be \"unarchived\" (ie. the \"inbox\" tag will be added instead of removed)." (interactive "P") - (if unarchive - (notmuch-show-add-tag "inbox") - (notmuch-show-remove-tag "inbox"))) + (let ((op (if unarchive "+" "-"))) + (notmuch-show-tag-message (concat op "inbox")))) (defun notmuch-show-archive-message-then-next () "Archive the current message, then show the next open message in the current thread." -- cgit v1.2.3 From 148a96c43d372333060d2bd0eef73a74659f8aa3 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:46 +0400 Subject: test: fix emacs tests after tagging operations changes After the recent tagging operations changes, functions bound to "+" and "-" in notmuch-search and notmuch-show views always read input from the minibuffer. Use kbd macros instead of calling them directly. --- test/emacs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/emacs b/test/emacs index 256a738..b74cfa9 100755 --- a/test/emacs +++ b/test/emacs @@ -101,26 +101,26 @@ test_begin_subtest "Add tag from search view" os_x_darwin_thread=$(notmuch search --output=threads id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com) test_emacs "(notmuch-search \"$os_x_darwin_thread\") (notmuch-test-wait) - (notmuch-search-add-tag \"tag-from-search-view\")" + (execute-kbd-macro \"+tag-from-search-view\")" output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-search-view unread)" test_begin_subtest "Remove tag from search view" test_emacs "(notmuch-search \"$os_x_darwin_thread\") (notmuch-test-wait) - (notmuch-search-remove-tag \"tag-from-search-view\")" + (execute-kbd-macro \"-tag-from-search-view\")" output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)" test_begin_subtest "Add tag from notmuch-show view" test_emacs "(notmuch-show \"$os_x_darwin_thread\") - (notmuch-show-add-tag \"tag-from-show-view\")" + (execute-kbd-macro \"+tag-from-show-view\")" output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-show-view unread)" test_begin_subtest "Remove tag from notmuch-show view" test_emacs "(notmuch-show \"$os_x_darwin_thread\") - (notmuch-show-remove-tag \"tag-from-show-view\")" + (execute-kbd-macro \"-tag-from-show-view\")" output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)" @@ -128,14 +128,14 @@ test_begin_subtest "Message with .. in Message-Id:" add_message [id]=123..456@example '[subject]="Message with .. in Message-Id"' test_emacs '(notmuch-search "id:\"123..456@example\"") (notmuch-test-wait) - (notmuch-search-add-tag "search-add") - (notmuch-search-add-tag "search-remove") - (notmuch-search-remove-tag "search-remove") + (execute-kbd-macro "+search-add") + (execute-kbd-macro "+search-remove") + (execute-kbd-macro "-search-remove") (notmuch-show "id:\"123..456@example\"") (notmuch-test-wait) - (notmuch-show-add-tag "show-add") - (notmuch-show-add-tag "show-remove") - (notmuch-show-remove-tag "show-remove")' + (execute-kbd-macro "+show-add") + (execute-kbd-macro "+show-remove") + (execute-kbd-macro "-show-remove")' output=$(notmuch search 'id:"123..456@example"' | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Message with .. in Message-Id (inbox search-add show-add)" -- cgit v1.2.3 From f643f1bac039a26471d46d8871bb73d9cabe5f7c Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:47 +0400 Subject: emacs: rename `notmuch-search-operate-all' to `notmuch-search-tag-all' `Notmuch-search-tag-all' is more clear and consistent with other tagging function names. --- emacs/notmuch.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 1b472dd..1f351a5 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -270,7 +270,7 @@ For a mouse binding, return nil." (define-key map "t" 'notmuch-search-filter-by-tag) (define-key map "f" 'notmuch-search-filter) (define-key map [mouse-1] 'notmuch-search-show-thread) - (define-key map "*" 'notmuch-search-operate-all) + (define-key map "*" 'notmuch-search-tag-all) (define-key map "a" 'notmuch-search-archive-thread) (define-key map "-" 'notmuch-search-remove-tag) (define-key map "+" 'notmuch-search-add-tag) @@ -419,7 +419,7 @@ any tags). Pressing \\[notmuch-search-show-thread] on any line displays that thread. The '\\[notmuch-search-add-tag]' and '\\[notmuch-search-remove-tag]' keys can be used to add or remove tags from a thread. The '\\[notmuch-search-archive-thread]' key is a convenience for archiving a thread (removing the \"inbox\" -tag). The '\\[notmuch-search-operate-all]' key can be used to add or remove a tag from all +tag). The '\\[notmuch-search-tag-all]' key can be used to add or remove a tag from all threads in the current buffer. Other useful commands are '\\[notmuch-search-filter]' for filtering the current search @@ -876,7 +876,7 @@ non-authors is found, assume that all of the authors match." (goto-char found-target))) (delete-process proc)))) -(defun notmuch-search-operate-all (&rest actions) +(defun notmuch-search-tag-all (&rest actions) "Add/remove tags from all matching messages. This command adds or removes tags from all messages matching the -- cgit v1.2.3 From 26d5b0efe4160b789670fbb059bea4630a352212 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:48 +0400 Subject: emacs: add "*" binding for notmuch-show view The patch adds `notmuch-show-tag-all' function bound to "*" in notmuch-show view. The function is similar to the `notmuch-search-tag-all' function for the notmuch-search view: it changes tags for all messages in the current thread. --- emacs/notmuch-show.el | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 48a2a60..faa9f9b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1084,6 +1084,7 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map "c" 'notmuch-show-stash-map) (define-key map "=" 'notmuch-show-refresh-view) (define-key map "h" 'notmuch-show-toggle-headers) + (define-key map "*" 'notmuch-show-tag-all) (define-key map "-" 'notmuch-show-remove-tag) (define-key map "+" 'notmuch-show-add-tag) (define-key map "x" 'notmuch-show-archive-thread-then-exit) @@ -1181,6 +1182,15 @@ All currently available key bindings: (notmuch-show-move-to-message-top) t)) +(defun notmuch-show-mapc (function) + "Iterate through all messages in the current thread with +`notmuch-show-goto-message-next' and call FUNCTION for side +effects." + (save-excursion + (goto-char (point-min)) + (loop do (funcall function) + while (notmuch-show-goto-message-next)))) + ;; Functions relating to the visibility of messages and their ;; components. @@ -1233,6 +1243,18 @@ Some useful entries are: "Return the message id of the current message." (concat "id:\"" (notmuch-show-get-prop :id) "\"")) +(defun notmuch-show-get-messages-ids () + "Return all message ids of messages in the current thread." + (let ((message-ids)) + (notmuch-show-mapc + (lambda () (push (notmuch-show-get-message-id) message-ids))) + message-ids)) + +(defun notmuch-show-get-messages-ids-search () + "Return a search string for all message ids of messages in the +current thread." + (mapconcat 'identity (notmuch-show-get-messages-ids) " or ")) + ;; dme: Would it make sense to use a macro for many of these? (defun notmuch-show-get-filename () @@ -1513,6 +1535,19 @@ TAG-CHANGES is a list of tag operations for `notmuch-tag'." initial-input (notmuch-show-get-message-id)))) (apply 'notmuch-show-tag-message tag-changes))) +(defun notmuch-show-tag-all (&rest tag-changes) + "Change tags for all messages in the current thread. + +TAG-CHANGES is a list of tag operations for `notmuch-tag'." + (interactive (notmuch-read-tag-changes nil notmuch-show-thread-id)) + (apply 'notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes) + (notmuch-show-mapc + (lambda () + (let* ((current-tags (notmuch-show-get-tags)) + (new-tags (notmuch-update-tags current-tags tag-changes))) + (unless (equal current-tags new-tags) + (notmuch-show-set-tags new-tags)))))) + (defun notmuch-show-add-tag () "Same as `notmuch-show-tag' but sets initial input to '+'." (interactive) -- cgit v1.2.3 From 904eafaefc356b654842ff59de6afc17f24ccf17 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:49 +0400 Subject: emacs: separate history for operations which accept single and multiple tags Some tag-related operations accept a single tag without prefix (`notmuch-select-tag-with-completion'), others accept multiple tags prefixed with '+' or '-' (`notmuch-read-tag-changes'). Before the change, both functions used a single default minibuffer history. This is inconvenient because you have to skip options with incompatible format when going through the history. The patch adds separate history lists for the two functions. Note that functions that accept the same input format (e.g. "+", "-", "*") share the history list as before. --- emacs/notmuch.el | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 1f351a5..862d9e8 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -76,6 +76,14 @@ For example: (defvar notmuch-query-history nil "Variable to store minibuffer history for notmuch queries") +(defvar notmuch-select-tag-history nil + "Variable to store minibuffer history for +`notmuch-select-tag-with-completion' function.") + +(defvar notmuch-read-tag-changes-history nil + "Variable to store minibuffer history for +`notmuch-read-tag-changes' function.") + (defun notmuch-tag-completions (&optional search-terms) (split-string (with-output-to-string @@ -86,7 +94,7 @@ For example: (defun notmuch-select-tag-with-completion (prompt &rest search-terms) (let ((tag-list (notmuch-tag-completions search-terms))) - (completing-read prompt tag-list))) + (completing-read prompt tag-list nil nil nil 'notmuch-select-tag-history))) (defun notmuch-read-tag-changes (&optional initial-input &rest search-terms) (let* ((all-tag-list (notmuch-tag-completions)) @@ -106,7 +114,8 @@ For example: (define-key map " " 'self-insert-command) map))) (delete "" (completing-read-multiple "Tags (+add -drop): " - tag-list nil nil initial-input)))) + tag-list nil nil initial-input + 'notmuch-read-tag-changes-history)))) (defun notmuch-update-tags (tags tag-changes) "Return a copy of TAGS with additions and removals from TAG-CHANGES. -- cgit v1.2.3 From 26fce4ae605d6315bd2732ba3607ac30138e67b4 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:50 +0400 Subject: emacs: relax tag syntax check in `notmuch-tag' function The tag syntax check in `notmuch-tag' function was too strict and did not allow nmbug tags with "::". Since the check is done for all tagging operations in Emacs UI, this basically means that no nmbug tags can be changed. The patch relaxes the tag syntax check to allow any tag names that do not include whitespace characters. --- emacs/notmuch.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 862d9e8..b06d8a1 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -550,7 +550,7 @@ notmuch-after-tag-hook will be run." ;; Perform some validation (when (null tags) (error "No tags given")) (mapc (lambda (tag) - (unless (string-match-p "^[-+][-+_.[:word:]]+$" tag) + (unless (string-match-p "^[-+]\\S-+$" tag) (error "Tag must be of the form `+this_tag' or `-that_tag'"))) tags) (run-hooks 'notmuch-before-tag-hook) -- cgit v1.2.3 From 9d8fa2acac9185a5c62fad1db49a1f5920143f9d Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:51 +0400 Subject: emacs: accept empty tag list in `notmuch-tag' Since `notmuch-tag' is a non-interactive function and hence is meant to be invoked programmatically, it should accept zero tags. Also, the tagging operations (bound to "*", "+", "-") would accept empty input without an error. --- emacs/notmuch.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index b06d8a1..0ffdf9c 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -548,15 +548,15 @@ messages instead of running (notmuch-call-notmuch-process \"tag\" ..) directly, so that hooks specified in notmuch-before-tag-hook and notmuch-after-tag-hook will be run." ;; Perform some validation - (when (null tags) (error "No tags given")) (mapc (lambda (tag) (unless (string-match-p "^[-+]\\S-+$" tag) (error "Tag must be of the form `+this_tag' or `-that_tag'"))) tags) - (run-hooks 'notmuch-before-tag-hook) - (apply 'notmuch-call-notmuch-process - (append (list "tag") tags (list "--" query))) - (run-hooks 'notmuch-after-tag-hook)) + (unless (null tags) + (run-hooks 'notmuch-before-tag-hook) + (apply 'notmuch-call-notmuch-process "tag" + (append tags (list "--" query))) + (run-hooks 'notmuch-after-tag-hook))) (defcustom notmuch-before-tag-hook nil "Hooks that are run before tags of a message are modified. -- cgit v1.2.3 From 6e59a5630f0ce0d4ae483ed68e914cb2bb8a5231 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:52 +0400 Subject: emacs: s/tags/tag-changes/ for arguments of tagging functions This makes the argument names more consistent and clear. The following functions changed: `notmuch-tag', `notmuch-search-tag-thread', `notmuch-search-tag-region' and `notmuch-search-tag-all'. --- emacs/notmuch.el | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 0ffdf9c..8250961 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -537,25 +537,26 @@ and will also appear in a buffer named \"*Notmuch errors*\"." (error (buffer-substring beg end)) )))))) -(defun notmuch-tag (query &rest tags) - "Add/remove tags in TAGS to messages matching QUERY. +(defun notmuch-tag (query &rest tag-changes) + "Add/remove tags in TAG-CHANGES to messages matching QUERY. -TAGS should be a list of strings of the form \"+TAG\" or \"-TAG\" and -QUERY should be a string containing the search-query. +TAG-CHANGES should be a list of strings of the form \"+tag\" or +\"-tag\" and QUERY should be a string containing the +search-query. Note: Other code should always use this function alter tags of messages instead of running (notmuch-call-notmuch-process \"tag\" ..) directly, so that hooks specified in notmuch-before-tag-hook and notmuch-after-tag-hook will be run." ;; Perform some validation - (mapc (lambda (tag) - (unless (string-match-p "^[-+]\\S-+$" tag) + (mapc (lambda (tag-change) + (unless (string-match-p "^[-+]\\S-+$" tag-change) (error "Tag must be of the form `+this_tag' or `-that_tag'"))) - tags) - (unless (null tags) + tag-changes) + (unless (null tag-changes) (run-hooks 'notmuch-before-tag-hook) (apply 'notmuch-call-notmuch-process "tag" - (append tags (list "--" query))) + (append tag-changes (list "--" query))) (run-hooks 'notmuch-after-tag-hook))) (defcustom notmuch-before-tag-hook nil @@ -615,26 +616,26 @@ the messages that were tagged" (forward-line 1)) output))) -(defun notmuch-search-tag-thread (&rest tags) +(defun notmuch-search-tag-thread (&rest tag-changes) "Change tags for the currently selected thread. See `notmuch-search-tag-region' for details." - (apply 'notmuch-search-tag-region (point) (point) tags)) + (apply 'notmuch-search-tag-region (point) (point) tag-changes)) -(defun notmuch-search-tag-region (beg end &rest tags) +(defun notmuch-search-tag-region (beg end &rest tag-changes) "Change tags for threads in the given region. TAGS is a list of tag operations for `notmuch-tag'. The tags are added or removed for all threads in the region from BEG to END." (let ((search-string (notmuch-search-find-thread-id-region-search beg end))) - (apply 'notmuch-tag search-string tags) + (apply 'notmuch-tag search-string tag-changes) (save-excursion (let ((last-line (line-number-at-pos end)) (max-line (- (line-number-at-pos (point-max)) 2))) (goto-char beg) (while (<= (line-number-at-pos) (min last-line max-line)) (notmuch-search-set-tags - (notmuch-update-tags (notmuch-search-get-tags) tags)) + (notmuch-update-tags (notmuch-search-get-tags) tag-changes)) (forward-line)))))) (defun notmuch-search-tag (&optional initial-input) @@ -885,7 +886,7 @@ non-authors is found, assume that all of the authors match." (goto-char found-target))) (delete-process proc)))) -(defun notmuch-search-tag-all (&rest actions) +(defun notmuch-search-tag-all (&rest tag-changes) "Add/remove tags from all matching messages. This command adds or removes tags from all messages matching the @@ -897,7 +898,7 @@ Each character of the tag name may consist of alphanumeric characters as well as `_.+-'. " (interactive (notmuch-read-tag-changes)) - (apply 'notmuch-tag notmuch-search-query-string actions)) + (apply 'notmuch-tag notmuch-search-query-string tag-changes)) (defun notmuch-search-buffer-title (query) "Returns the title for a buffer with notmuch search results." -- cgit v1.2.3 From 61fa5c0bb88fd3cd4a52d5a5b93a12bed58c063e Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 5 Feb 2012 11:13:53 +0400 Subject: NEWS: document Emacs UI tagging operations changes --- NEWS | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/NEWS b/NEWS index 5c5b645..f449fba 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,24 @@ Reply to sender and search modes, 'r' has been bound to reply to sender, replacing reply to all, which now has key binding 'R'. +More flexible and consistent tagging operations + + All tagging operations ("+", "-", "*") now accept multiple tags with + "+" or "-" prefix, like "*" operation in notmuch-search view before. + + "*" operation (`notmuch-show-tag-all') is now available in + notmuch-show view. + + `Notmuch-show-{add,remove}-tag' functions no longer accept tag + argument, `notmuch-show-tag-message' should be used instead. Custom + bindings using these functions should be updated, e.g.: + + (notmuch-show-remove-tag "unread") + + should be changed to: + + (notmuch-show-tag-message "-unread") + Library changes --------------- -- cgit v1.2.3 From d2ef4edc545ea02764aa005bc3e56cdb4310ce09 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 31 Jan 2012 19:29:06 +0200 Subject: emacs: make show view a/A/x/X key bindings more consistent Modify the show view key bindings as follows to make them more consistent: 'a' = Archive current message, then move to next message, or show next thread from search if at the last message in thread. 'A' = Archive each message in thread, then show next thread from search. 'x' = Archive current message, then move to next message, or exit back to search results if at the last message in thread. 'X' = Archive each message in thread, then exit back to search results. The changes make the key bindings more consistent in two ways: 1) 'a'/'A' both advance to the next thread like 'a' used to. 2) 'x' operates on messages and 'X' on threads like 'a'/'A'. --- emacs/notmuch-show.el | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index faa9f9b..24fde05 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1087,9 +1087,10 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map "*" 'notmuch-show-tag-all) (define-key map "-" 'notmuch-show-remove-tag) (define-key map "+" 'notmuch-show-add-tag) - (define-key map "x" 'notmuch-show-archive-thread-then-exit) - (define-key map "a" 'notmuch-show-archive-message-then-next) + (define-key map "X" 'notmuch-show-archive-thread-then-exit) + (define-key map "x" 'notmuch-show-archive-message-then-next-or-exit) (define-key map "A" 'notmuch-show-archive-thread-then-next) + (define-key map "a" 'notmuch-show-archive-message-then-next-or-next-thread) (define-key map "N" 'notmuch-show-next-message) (define-key map "P" 'notmuch-show-previous-message) (define-key map "n" 'notmuch-show-next-open-message) @@ -1452,7 +1453,8 @@ thread, navigate to the next thread in the parent search buffer." If a prefix argument is given and this is the last open message in the thread, navigate to the next thread in the parent search -buffer." +buffer. Return t if there was a next open message in the thread +to show, nil otherwise." (interactive "P") (let (r) (while (and (setq r (notmuch-show-goto-message-next)) @@ -1463,7 +1465,8 @@ buffer." (notmuch-show-message-adjust)) (if pop-at-end (notmuch-show-next-thread) - (goto-char (point-max)))))) + (goto-char (point-max)))) + r)) (defun notmuch-show-previous-open-message () "Show the previous open message." @@ -1671,12 +1674,25 @@ removed)." (let ((op (if unarchive "+" "-"))) (notmuch-show-tag-message (concat op "inbox")))) -(defun notmuch-show-archive-message-then-next () - "Archive the current message, then show the next open message in the current thread." +(defun notmuch-show-archive-message-then-next-or-exit () + "Archive the current message, then show the next open message in the current thread. + +If at the last open message in the current thread, then exit back +to search results." (interactive) (notmuch-show-archive-message) (notmuch-show-next-open-message t)) +(defun notmuch-show-archive-message-then-next-or-next-thread () + "Archive the current message, then show the next open message in the current thread. + +If at the last open message in the current thread, then show next +thread from search." + (interactive) + (notmuch-show-archive-message) + (unless (notmuch-show-next-open-message) + (notmuch-show-next-thread t))) + (defun notmuch-show-stash-cc () "Copy CC field of current message to kill-ring." (interactive) -- cgit v1.2.3 From 8c5be7d12dc5e48faf4008209327d34c04961616 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 10 Feb 2012 18:52:19 +0100 Subject: python: Add a Mock class to the sphinx config that can be used to mock modules Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/conf.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py index e0ee39c..c7b9518 100644 --- a/bindings/python/docs/source/conf.py +++ b/bindings/python/docs/source/conf.py @@ -18,6 +18,23 @@ import sys, os # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0,os.path.abspath('../..')) +class Mock(object): + def __init__(self, *args, **kwargs): + pass + + def __call__(self, *args, **kwargs): + return Mock() + + @classmethod + def __getattr__(self, name): + return Mock() if name not in ('__file__', '__path__') else '/dev/null' + +MOCK_MODULES = [ +] +for mod_name in MOCK_MODULES: + sys.modules[mod_name] = Mock() + + from notmuch import __VERSION__,__AUTHOR__ # -- General configuration ----------------------------------------------------- -- cgit v1.2.3 From ae376c774ee8d8f45bae002261c425e1bc0a243a Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 10 Feb 2012 18:53:04 +0100 Subject: python: mock out the ctypes library This allows rtfd.org to build the documentation without libnotmuch. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py index c7b9518..76610b7 100644 --- a/bindings/python/docs/source/conf.py +++ b/bindings/python/docs/source/conf.py @@ -30,6 +30,7 @@ class Mock(object): return Mock() if name not in ('__file__', '__path__') else '/dev/null' MOCK_MODULES = [ + 'ctypes', ] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() -- cgit v1.2.3 From bb514d7862aa38f6628ef1c8cb69ed5ec72098fd Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 10 Feb 2012 22:34:47 +0100 Subject: py3k: Fix decoding of default database name in Database._get_user_default_db Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 6238b28..36b65ec 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -18,6 +18,7 @@ Copyright 2010 Sebastian Spaeth ' """ import os +import codecs from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError, NullPointerError, Enum, _str, @@ -553,11 +554,11 @@ class Database(object): config = SafeConfigParser() conf_f = os.getenv('NOTMUCH_CONFIG', os.path.expanduser('~/.notmuch-config')) - config.read(conf_f) + config.readfp(codecs.open(conf_f, 'r', 'utf-8')) if not config.has_option('database', 'path'): raise NotmuchError(message="No DB path specified" " and no user default found") - return config.get('database', 'path').decode('utf-8') + return config.get('database', 'path') @property def db_p(self): -- cgit v1.2.3 From 022a2810809ffba4a7bcb71ed3483d2cb29d1c77 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Fri, 27 Jan 2012 19:46:58 -0400 Subject: STYLE: Initial draft of coding style document This was edited by (at least) Austin, Tomi, and myself. Amended with Austin's proposed wording for indentation. --- devel/STYLE | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 devel/STYLE diff --git a/devel/STYLE b/devel/STYLE new file mode 100644 index 0000000..094f71d --- /dev/null +++ b/devel/STYLE @@ -0,0 +1,88 @@ +C/C++ coding style +================== + +Tools +----- + +There is a file uncrustify.cfg in this directory that can be used to +approximate the prevailing code style. You can run it with e.g. + + uncrustify --replace -c devel/uncrustify.cfg foo.c + +You still have to use your judgement about accepting or rejecting the +changes uncrustify makes. With a nice git frontend, you can add the +lines you agree with and reject the rest. + +For Emacs users, the file .dir-locals.el in the top level source +directory will configure c-mode to automatically meet most of the +basic layout rules. I + +Indentation, Whitespace, and Layout +----------------------------------- + +The following nonsense code demonstrates many aspects of the style: + +static some_type +function (param_type param, param_type param) +{ + int i; + + for (i = 0; i < 10; i++) { + int j; + + j = i + 10; + + some_other_func (j, i); + } +} + +* Indent is 4 spaces with mixed tab/spaces and a tab width of 8. + (Specifically, a line should begin with zero or more tabs followed + by fewer than eight spaces.) + +* Use copious whitespace. In particular + - there is a space between the function name and the open paren in a call. + - likewise, there is a space following keywords such as if and while + - every binary operator should have space on either side. + +* No trailing whitespace. Please enable the standard pre-commit hook + in git (or an equivalent hook). + +* The name in a function prototype should start at the beginning of a line. + +* Opening braces "cuddle" (they are on the same line as the + if/for/while test) and are preceded by a space. The opening brace of + functions is the exception, and starts on a new line. + +* Comments are always C-style /* */ block comments. They should start + with a capital letter and generally be written in complete + sentences. Public library functions are documented immediately + before their prototype in lib/notmuch.h. Internal functions are + typically documented immediately before their definition. + +* Code lines should be less than 80 columns and comments should be + wrapped at 70 columns. + +Naming +------ + +* Use lowercase_with_underscores for function, variable, and type + names. + +* All structs should be typedef'd to a name ending with _t. If the + struct has a tag, it should be the same as the typedef name, minus + the trailing _t. + +libnotmuch conventions +---------------------------------- + +* Functions starting with notmuch_ in lib/notmuch.h are public and are + automatically exported from the shared library. Private library + functions should generally either be static or, if they are shared + between compilation units, start with _notmuch. + +* Functions in libnotmuch must not access user configuration files + (i.e. .notmuch-config) + +* Code which needs to be accessed from both the CLI and from + libnotmuch should be factored out into libutil (under util/). -- cgit v1.2.3 From 754ddbb578175b986b10099cb4a11517080579f7 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 29 Jan 2012 07:36:02 +0400 Subject: test: auto load elisp tests file in test_emacs if available This allows us to simplify shell part of tests written in elisp. --- test/test-lib.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test-lib.sh b/test/test-lib.sh index 8158328..0174e93 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -943,6 +943,12 @@ test_emacs () { test -z "$missing_dependencies" || return if [ -z "$EMACS_SERVER" ]; then + emacs_tests="$(basename $0).el" + if [ -f "$TEST_DIRECTORY/$emacs_tests" ]; then + load_emacs_tests="--eval '(load \"$emacs_tests\")'" + else + load_emacs_tests= + fi server_name="notmuch-test-suite-$$" # start a detached session with an emacs server # user's TERM is given to dtach which assumes a minimally @@ -950,6 +956,7 @@ test_emacs () { TERM=$ORIGINAL_TERM dtach -n "$TEST_TMPDIR/emacs-dtach-socket.$$" \ sh -c "stty rows 24 cols 80; exec '$TMP_DIRECTORY/run_emacs' \ --no-window-system \ + $load_emacs_tests \ --eval '(setq server-name \"$server_name\")' \ --eval '(server-start)' \ --eval '(orphan-watchdog $$)'" || return -- cgit v1.2.3 From 8d26b8eaac6dc7eebc2658587b2656555244f4b3 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sun, 29 Jan 2012 07:36:03 +0400 Subject: test: remove explicit loading of elisp tests in emacs-address-cleaning It is no longer needed, since elisp tests files are auto loaded now. --- test/emacs-address-cleaning | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/emacs-address-cleaning b/test/emacs-address-cleaning index 51018fe..6ddde5c 100755 --- a/test/emacs-address-cleaning +++ b/test/emacs-address-cleaning @@ -4,15 +4,12 @@ test_description="emacs address cleaning" . test-lib.sh test_begin_subtest "notmuch-test-address-clean part 1" -test_emacs_expect_t \ - '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-1)' +test_emacs_expect_t '(notmuch-test-address-cleaning-1)' test_begin_subtest "notmuch-test-address-clean part 2" -test_emacs_expect_t \ - '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-2)' +test_emacs_expect_t '(notmuch-test-address-cleaning-2)' test_begin_subtest "notmuch-test-address-clean part 3" -test_emacs_expect_t \ - '(load "emacs-address-cleaning.el") (notmuch-test-address-cleaning-3)' +test_emacs_expect_t '(notmuch-test-address-cleaning-3)' test_done -- cgit v1.2.3 From e06943f85ea31721d375731b93e5468570d1c375 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 2 Feb 2012 16:58:41 +0200 Subject: emacs: add default value to notmuch-search-line-faces Add default value to notmuch-search-line-faces to show "unread" messages in bold, and "flagged" messages in blue, to have some visual indication of important messages in search results. This should be helpful for new users. "unread" tag is quite obvious, and handled specially both in the lib and emacs ui. "flagged" is synced to maildir F flag in the lib. If one syncs the maildir to IMAP, this also translates to corresponding IMAP flag. (This is "starred" in GMail and Android.) Signed-off-by: Jani Nikula --- emacs/notmuch.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 8250961..11113fe 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -699,7 +699,8 @@ This function advances the next thread when finished." (goto-char (point-min)) (forward-line (1- notmuch-search-target-line)))))))) -(defcustom notmuch-search-line-faces nil +(defcustom notmuch-search-line-faces '(("unread" :weight bold) + ("flagged" :foreground "blue")) "Tag/face mapping for line highlighting in notmuch-search. Here is an example of how to color search results based on tags. -- cgit v1.2.3 From 85fe286b85bda6cddf509308b967f13f44f01331 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 4 Feb 2012 16:24:25 -0500 Subject: show: Convert text format to the new self-recursive style This is all code movement and a smidgen of glue. This moves the existing text formatter code into one self-recursive function, but doesn't change any of the logic. The next patch will actually take advantage of what the new structure has to offer. Note that this patch retains format_headers_message_part_text because it is also used by the raw format. --- notmuch-show.c | 270 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 139 insertions(+), 131 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index dec799c..6a890b2 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -20,41 +20,18 @@ #include "notmuch-client.h" -static void -format_message_text (unused (const void *ctx), - notmuch_message_t *message, - int indent); -static void -format_headers_text (const void *ctx, - notmuch_message_t *message); - static void format_headers_message_part_text (GMimeMessage *message); static void -format_part_start_text (GMimeObject *part, - int *part_count); - -static void -format_part_content_text (GMimeObject *part); - -static void -format_part_end_text (GMimeObject *part); +format_part_text (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_text = { - "", NULL, - "\fmessage{ ", format_message_text, - "\fheader{\n", format_headers_text, format_headers_message_part_text, "\fheader}\n", - "\fbody{\n", - format_part_start_text, - NULL, - NULL, - format_part_content_text, - format_part_end_text, - "", - "\fbody}\n", - "\fmessage}\n", "", - "" + .message_set_start = "", + .part = format_part_text, + .message_set_sep = "", + .message_set_end = "" }; static void @@ -190,16 +167,6 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message) from, relative_date, tags); } -static void -format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent) -{ - printf ("id:%s depth:%d match:%d filename:%s\n", - notmuch_message_get_message_id (message), - indent, - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), - notmuch_message_get_filename (message)); -} - static void format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent)) { @@ -338,26 +305,6 @@ format_message_mbox (const void *ctx, fclose (file); } - -static void -format_headers_text (const void *ctx, notmuch_message_t *message) -{ - const char *headers[] = { - "Subject", "From", "To", "Cc", "Bcc", "Date" - }; - const char *name, *value; - unsigned int i; - - printf ("%s\n", _get_one_line_summary (ctx, message)); - - for (i = 0; i < ARRAY_SIZE (headers); i++) { - name = headers[i]; - value = notmuch_message_get_header (message, name); - if (value && strlen (value)) - printf ("%s: %s\n", name, value); - } -} - static void format_headers_message_part_text (GMimeMessage *message) { @@ -522,78 +469,6 @@ signer_status_to_string (GMimeSignerStatus x) } #endif -static void -format_part_start_text (GMimeObject *part, int *part_count) -{ - GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part); - - if (disposition && - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment{ ID: %d", *part_count); - - } else { - - printf ("\fpart{ ID: %d", *part_count); - } -} - -static void -format_part_content_text (GMimeObject *part) -{ - const char *cid = g_mime_object_get_content_id (part); - GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); - - if (GMIME_IS_PART (part)) - { - const char *filename = g_mime_part_get_filename (GMIME_PART (part)); - if (filename) - printf (", Filename: %s", filename); - } - - if (cid) - printf (", Content-id: %s", cid); - - printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); - - if (g_mime_content_type_is_type (content_type, "text", "*") && - !g_mime_content_type_is_type (content_type, "text", "html")) - { - GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); - show_text_part_content (part, stream_stdout); - g_object_unref(stream_stdout); - } - else if (g_mime_content_type_is_type (content_type, "multipart", "*") || - g_mime_content_type_is_type (content_type, "message", "rfc822")) - { - /* Do nothing for multipart since its content will be printed - * when recursing. */ - } - else - { - printf ("Non-text part: %s\n", - g_mime_content_type_to_string (content_type)); - } -} - -static void -format_part_end_text (GMimeObject *part) -{ - GMimeContentDisposition *disposition; - - disposition = g_mime_object_get_content_disposition (part); - if (disposition && - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment}\n"); - } - else - { - printf ("\fpart}\n"); - } -} - static void format_part_start_json (unused (GMimeObject *part), int *part_count) { @@ -843,6 +718,139 @@ format_part_content_raw (GMimeObject *part) g_object_unref(stream_stdout); } +static void +format_part_text (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params) +{ + /* The disposition and content-type metadata are associated with + * the envelope for message parts */ + GMimeObject *meta = node->envelope_part ? + GMIME_OBJECT (node->envelope_part) : node->part; + GMimeContentType *content_type = g_mime_object_get_content_type (meta); + int i; + + if (node->envelope_file) { + notmuch_message_t *message = node->envelope_file; + const char *headers[] = { + "Subject", "From", "To", "Cc", "Bcc", "Date" + }; + const char *name, *value; + unsigned int i; + + printf ("\fmessage{ "); + printf ("id:%s depth:%d match:%d filename:%s\n", + notmuch_message_get_message_id (message), + indent, + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), + notmuch_message_get_filename (message)); + + printf ("\fheader{\n"); + + printf ("%s\n", _get_one_line_summary (ctx, message)); + + for (i = 0; i < ARRAY_SIZE (headers); i++) { + name = headers[i]; + value = notmuch_message_get_header (message, name); + if (value && strlen (value)) + printf ("%s: %s\n", name, value); + } + printf ("\fheader}\n"); + } else { + GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); + const char *cid = g_mime_object_get_content_id (meta); + + if (disposition && + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) + { + printf ("\fattachment{ ID: %d", node->part_num); + + } else { + + printf ("\fpart{ ID: %d", node->part_num); + } + + if (GMIME_IS_PART (node->part)) + { + const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); + if (filename) + printf (", Filename: %s", filename); + } + + if (cid) + printf (", Content-id: %s", cid); + + printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); + } + + if (node->envelope_part) { + GMimeMessage *message = GMIME_MESSAGE (node->part); + InternetAddressList *recipients; + const char *recipients_string; + + printf ("\fheader{\n"); + printf ("Subject: %s\n", g_mime_message_get_subject (message)); + printf ("From: %s\n", g_mime_message_get_sender (message)); + recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); + recipients_string = internet_address_list_to_string (recipients, 0); + if (recipients_string) + printf ("To: %s\n", recipients_string); + recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); + recipients_string = internet_address_list_to_string (recipients, 0); + if (recipients_string) + printf ("Cc: %s\n", recipients_string); + printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); + printf ("\fheader}\n"); + } + + if (!node->envelope_file) { + if (g_mime_content_type_is_type (content_type, "text", "*") && + !g_mime_content_type_is_type (content_type, "text", "html")) + { + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); + show_text_part_content (node->part, stream_stdout); + g_object_unref(stream_stdout); + } + else if (g_mime_content_type_is_type (content_type, "multipart", "*") || + g_mime_content_type_is_type (content_type, "message", "rfc822")) + { + /* Do nothing for multipart since its content will be printed + * when recursing. */ + } + else + { + printf ("Non-text part: %s\n", + g_mime_content_type_to_string (content_type)); + } + } + + if (GMIME_IS_MESSAGE (node->part)) + printf ("\fbody{\n"); + + for (i = 0; i < node->nchildren; i++) + format_part_text (ctx, mime_node_child (node, i), indent, params); + + if (GMIME_IS_MESSAGE (node->part)) + printf ("\fbody}\n"); + + if (node->envelope_file) { + printf ("\fmessage}\n"); + } else { + GMimeContentDisposition *disposition; + + disposition = g_mime_object_get_content_disposition (meta); + if (disposition && + strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) + { + printf ("\fattachment}\n"); + } + else + { + printf ("\fpart}\n"); + } + } +} + static void show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From c0cd09041208abb6d641279c9ae453e6f2fdf4db Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 4 Feb 2012 16:24:26 -0500 Subject: show: Simplify new text formatter code This makes the text formatter take advantage of the new code structure. The previously duplicated header logic is now unified, several things that we used to compute repeatedly across different callbacks are now computed once, and the code is simpler overall and 32% shorter. Unifying the header logic causes this to format some dates slightly differently, so the two affected test cases are updated. --- notmuch-show.c | 87 ++++++++++++++---------------------------------------- test/crypto | 2 +- test/thread-naming | 16 +++++----- 3 files changed, 31 insertions(+), 74 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 6a890b2..816e0f8 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -727,67 +727,48 @@ format_part_text (const void *ctx, mime_node_t *node, GMimeObject *meta = node->envelope_part ? GMIME_OBJECT (node->envelope_part) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); + const notmuch_bool_t leaf = GMIME_IS_PART (node->part); + const char *part_type; int i; if (node->envelope_file) { notmuch_message_t *message = node->envelope_file; - const char *headers[] = { - "Subject", "From", "To", "Cc", "Bcc", "Date" - }; - const char *name, *value; - unsigned int i; - - printf ("\fmessage{ "); - printf ("id:%s depth:%d match:%d filename:%s\n", + + part_type = "message"; + printf ("\f%s{ id:%s depth:%d match:%d filename:%s\n", + part_type, notmuch_message_get_message_id (message), indent, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), notmuch_message_get_filename (message)); - - printf ("\fheader{\n"); - - printf ("%s\n", _get_one_line_summary (ctx, message)); - - for (i = 0; i < ARRAY_SIZE (headers); i++) { - name = headers[i]; - value = notmuch_message_get_header (message, name); - if (value && strlen (value)) - printf ("%s: %s\n", name, value); - } - printf ("\fheader}\n"); } else { GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); const char *cid = g_mime_object_get_content_id (meta); + const char *filename = leaf ? + g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; if (disposition && strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment{ ID: %d", node->part_num); - - } else { - - printf ("\fpart{ ID: %d", node->part_num); - } - - if (GMIME_IS_PART (node->part)) - { - const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); - if (filename) - printf (", Filename: %s", filename); - } + part_type = "attachment"; + else + part_type = "part"; + printf ("\f%s{ ID: %d", part_type, node->part_num); + if (filename) + printf (", Filename: %s", filename); if (cid) printf (", Content-id: %s", cid); - printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); } - if (node->envelope_part) { + if (GMIME_IS_MESSAGE (node->part)) { GMimeMessage *message = GMIME_MESSAGE (node->part); InternetAddressList *recipients; const char *recipients_string; printf ("\fheader{\n"); + if (node->envelope_file) + printf ("%s\n", _get_one_line_summary (ctx, node->envelope_file)); printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("From: %s\n", g_mime_message_get_sender (message)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); @@ -800,9 +781,11 @@ format_part_text (const void *ctx, mime_node_t *node, printf ("Cc: %s\n", recipients_string); printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); printf ("\fheader}\n"); + + printf ("\fbody{\n"); } - if (!node->envelope_file) { + if (leaf) { if (g_mime_content_type_is_type (content_type, "text", "*") && !g_mime_content_type_is_type (content_type, "text", "html")) { @@ -810,45 +793,19 @@ format_part_text (const void *ctx, mime_node_t *node, g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); show_text_part_content (node->part, stream_stdout); g_object_unref(stream_stdout); - } - else if (g_mime_content_type_is_type (content_type, "multipart", "*") || - g_mime_content_type_is_type (content_type, "message", "rfc822")) - { - /* Do nothing for multipart since its content will be printed - * when recursing. */ - } - else - { + } else { printf ("Non-text part: %s\n", g_mime_content_type_to_string (content_type)); } } - if (GMIME_IS_MESSAGE (node->part)) - printf ("\fbody{\n"); - for (i = 0; i < node->nchildren; i++) format_part_text (ctx, mime_node_child (node, i), indent, params); if (GMIME_IS_MESSAGE (node->part)) printf ("\fbody}\n"); - if (node->envelope_file) { - printf ("\fmessage}\n"); - } else { - GMimeContentDisposition *disposition; - - disposition = g_mime_object_get_content_disposition (meta); - if (disposition && - strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) - { - printf ("\fattachment}\n"); - } - else - { - printf ("\fpart}\n"); - } - } + printf ("\f%s}\n", part_type); } static void diff --git a/test/crypto b/test/crypto index 446a58b..1dbb60a 100755 --- a/test/crypto +++ b/test/crypto @@ -159,7 +159,7 @@ Notmuch Test Suite (2000-01-01) (encrypted inbox) Subject: test encrypted message 001 From: Notmuch Test Suite To: test_suite@notmuchmail.org -Date: 01 Jan 2000 12:00:00 -0000 +Date: Sat, 01 Jan 2000 12:00:00 +0000 header} body{ part{ ID: 1, Content-type: multipart/encrypted diff --git a/test/thread-naming b/test/thread-naming index 2ce9216..942e593 100755 --- a/test/thread-naming +++ b/test/thread-naming @@ -71,7 +71,7 @@ Notmuch Test Suite (2001-01-05) (unread) Subject: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Fri, 05 Jan 2001 15:43:56 -0000 +Date: Fri, 05 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -85,7 +85,7 @@ Notmuch Test Suite (2001-01-06) (inbox unread) Subject: thread-naming: Older changed subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Sat, 06 Jan 2001 15:43:56 -0000 +Date: Sat, 06 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -99,7 +99,7 @@ Notmuch Test Suite (2001-01-07) (inbox unread) Subject: thread-naming: Newer changed subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Sun, 07 Jan 2001 15:43:56 -0000 +Date: Sun, 07 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -113,7 +113,7 @@ Notmuch Test Suite (2001-01-08) (unread) Subject: thread-naming: Final thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Mon, 08 Jan 2001 15:43:56 -0000 +Date: Mon, 08 Jan 2001 15:43:56 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -127,7 +127,7 @@ Notmuch Test Suite (2001-01-09) (inbox unread) Subject: Re: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Tue, 09 Jan 2001 15:43:45 -0000 +Date: Tue, 09 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -141,7 +141,7 @@ Notmuch Test Suite (2001-01-10) (inbox unread) Subject: Aw: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Wed, 10 Jan 2001 15:43:45 -0000 +Date: Wed, 10 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -155,7 +155,7 @@ Notmuch Test Suite (2001-01-11) (inbox unread) Subject: Vs: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Thu, 11 Jan 2001 15:43:45 -0000 +Date: Thu, 11 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain @@ -169,7 +169,7 @@ Notmuch Test Suite (2001-01-12) (inbox unread) Subject: Sv: thread-naming: Initial thread subject From: Notmuch Test Suite To: Notmuch Test Suite -Date: Fri, 12 Jan 2001 15:43:45 -0000 +Date: Fri, 12 Jan 2001 15:43:45 +0000 header} body{ part{ ID: 1, Content-type: text/plain -- cgit v1.2.3 From c9c5a6f70c8371809f3b079e1aba3de3b4a13f6b Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 6 Feb 2012 21:57:21 +0200 Subject: cli: use notmuch_bool_t for boolean fields in notmuch_show_params_t Use notmuch_bool_t instead of int for entire_thread, raw, and decrypt boolean fields in notmuch_show_params_t. No functional changes. Signed-off-by: Jani Nikula --- notmuch-client.h | 6 +++--- notmuch-reply.c | 7 +++---- notmuch-show.c | 14 +++++++------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index e0eb594..60828aa 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -98,15 +98,15 @@ typedef struct notmuch_show_format { } notmuch_show_format_t; typedef struct notmuch_show_params { - int entire_thread; - int raw; + notmuch_bool_t entire_thread; + notmuch_bool_t raw; int part; #ifdef GMIME_ATLEAST_26 GMimeCryptoContext* cryptoctx; #else GMimeCipherContext* cryptoctx; #endif - int decrypt; + notmuch_bool_t decrypt; } notmuch_show_params_t; /* There's no point in continuing when we've detected that we've done diff --git a/notmuch-reply.c b/notmuch-reply.c index f55b1d2..6b244e6 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -661,7 +661,6 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_show_params_t params = { .part = -1 }; int format = FORMAT_DEFAULT; int reply_all = TRUE; - notmuch_bool_t decrypt = FALSE; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format, "format", 'f', @@ -672,7 +671,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) (notmuch_keyword_t []){ { "all", TRUE }, { "sender", FALSE }, { 0, 0 } } }, - { NOTMUCH_OPT_BOOLEAN, &decrypt, "decrypt", 'd', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, { 0, 0, 0, 0, 0 } }; @@ -687,7 +686,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) else reply_format_func = notmuch_reply_format_default; - if (decrypt) { + if (params.decrypt) { #ifdef GMIME_ATLEAST_26 /* TODO: GMimePasswordRequestFunc */ params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); @@ -697,8 +696,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) #endif if (params.cryptoctx) { g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); - params.decrypt = TRUE; } else { + params.decrypt = FALSE; fprintf (stderr, "Failed to construct gpg context.\n"); } #ifndef GMIME_ATLEAST_26 diff --git a/notmuch-show.c b/notmuch-show.c index 816e0f8..13a6f54 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1028,11 +1028,11 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) int format_specified = 0; int i; - params.entire_thread = 0; - params.raw = 0; + params.entire_thread = FALSE; + params.raw = FALSE; params.part = -1; params.cryptoctx = NULL; - params.decrypt = 0; + params.decrypt = FALSE; argc--; argv++; /* skip subcommand argument */ @@ -1047,13 +1047,13 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) format = &format_text; } else if (strcmp (opt, "json") == 0) { format = &format_json; - params.entire_thread = 1; + params.entire_thread = TRUE; } else if (strcmp (opt, "mbox") == 0) { format = &format_mbox; mbox = 1; } else if (strcmp (opt, "raw") == 0) { format = &format_raw; - params.raw = 1; + params.raw = TRUE; } else { fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; @@ -1062,7 +1062,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) { params.part = atoi(argv[i] + sizeof ("--part=") - 1); } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) { - params.entire_thread = 1; + params.entire_thread = TRUE; } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { if (params.cryptoctx == NULL) { @@ -1082,7 +1082,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) #endif } if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) - params.decrypt = 1; + params.decrypt = TRUE; } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; -- cgit v1.2.3 From 2d09775baf1591e51ef7a8911655f2a5e9651c47 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 6 Feb 2012 21:57:22 +0200 Subject: cli: convert "notmuch show" to use the new argument parser Use the new notmuch argument parser to handle arguments in "notmuch show". There are three minor functional changes: 1) Also set params.raw = TRUE when defaulting to raw format when part is requested but format is not specified. This was a bug, and --part=0 without --format=raw did not work previously. 2) Set params.decrypt = FALSE if crypto context creation fails. 3) Only use the parameters for the last --format if specified multiple times. Previously this could have resulted in a non-working mixture of parameters. Signed-off-by: Jani Nikula --- notmuch-show.c | 149 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 13a6f54..8f72e58 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1014,6 +1014,14 @@ do_show (void *ctx, return 0; } +enum { + NOTMUCH_FORMAT_NOT_SPECIFIED, + NOTMUCH_FORMAT_JSON, + NOTMUCH_FORMAT_TEXT, + NOTMUCH_FORMAT_MBOX, + NOTMUCH_FORMAT_RAW +}; + int notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { @@ -1021,92 +1029,94 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_database_t *notmuch; notmuch_query_t *query; char *query_string; - char *opt; + int opt_index; const notmuch_show_format_t *format = &format_text; - notmuch_show_params_t params; - int mbox = 0; - int format_specified = 0; - int i; + notmuch_show_params_t params = { .part = -1 }; + int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; + notmuch_bool_t verify = FALSE; + + notmuch_opt_desc_t options[] = { + { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', + (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, + { "text", NOTMUCH_FORMAT_TEXT }, + { "mbox", NOTMUCH_FORMAT_MBOX }, + { "raw", NOTMUCH_FORMAT_RAW }, + { 0, 0 } } }, + { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, + { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, + { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, + { 0, 0, 0, 0, 0 } + }; - params.entire_thread = FALSE; - params.raw = FALSE; - params.part = -1; - params.cryptoctx = NULL; - params.decrypt = FALSE; + opt_index = parse_arguments (argc, argv, options, 1); + if (opt_index < 0) { + /* diagnostics already printed */ + return 1; + } - argc--; argv++; /* skip subcommand argument */ + if (format_sel == NOTMUCH_FORMAT_NOT_SPECIFIED) { + /* if part was requested and format was not specified, use format=raw */ + if (params.part >= 0) + format_sel = NOTMUCH_FORMAT_RAW; + else + format_sel = NOTMUCH_FORMAT_TEXT; + } - for (i = 0; i < argc && argv[i][0] == '-'; i++) { - if (strcmp (argv[i], "--") == 0) { - i++; - break; + switch (format_sel) { + case NOTMUCH_FORMAT_JSON: + format = &format_json; + params.entire_thread = TRUE; + break; + case NOTMUCH_FORMAT_TEXT: + format = &format_text; + break; + case NOTMUCH_FORMAT_MBOX: + if (params.part > 0) { + fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n"); + return 1; } - if (STRNCMP_LITERAL (argv[i], "--format=") == 0) { - opt = argv[i] + sizeof ("--format=") - 1; - if (strcmp (opt, "text") == 0) { - format = &format_text; - } else if (strcmp (opt, "json") == 0) { - format = &format_json; - params.entire_thread = TRUE; - } else if (strcmp (opt, "mbox") == 0) { - format = &format_mbox; - mbox = 1; - } else if (strcmp (opt, "raw") == 0) { - format = &format_raw; - params.raw = TRUE; - } else { - fprintf (stderr, "Invalid value for --format: %s\n", opt); - return 1; - } - format_specified = 1; - } else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) { - params.part = atoi(argv[i] + sizeof ("--part=") - 1); - } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) { - params.entire_thread = TRUE; - } else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) || - (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) { - if (params.cryptoctx == NULL) { + format = &format_mbox; + break; + case NOTMUCH_FORMAT_RAW: + format = &format_raw; + /* If --format=raw specified without specifying part, we can only + * output single message, so set part=0 */ + if (params.part < 0) + params.part = 0; + params.raw = TRUE; + break; + } + + if (params.decrypt || verify) { #ifdef GMIME_ATLEAST_26 - /* TODO: GMimePasswordRequestFunc */ - if (NULL == (params.cryptoctx = g_mime_gpg_context_new(NULL, "gpg"))) + /* TODO: GMimePasswordRequestFunc */ + params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg"); #else - GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL); - if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) + GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL); + params.cryptoctx = g_mime_gpg_context_new (session, "gpg"); #endif - fprintf (stderr, "Failed to construct gpg context.\n"); - else - g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE); -#ifndef GMIME_ATLEAST_26 - g_object_unref (session); - session = NULL; -#endif - } - if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0) - params.decrypt = TRUE; + if (params.cryptoctx) { + g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE); } else { - fprintf (stderr, "Unrecognized option: %s\n", argv[i]); - return 1; + params.decrypt = FALSE; + fprintf (stderr, "Failed to construct gpg context.\n"); } +#ifndef GMIME_ATLEAST_26 + g_object_unref (session); +#endif } - argc -= i; - argv += i; - config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; - query_string = query_string_from_args (ctx, argc, argv); + query_string = query_string_from_args (ctx, argc-opt_index, argv+opt_index); if (query_string == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } - if (mbox && params.part > 0) { - fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n"); - return 1; - } - if (*query_string == '\0') { fprintf (stderr, "Error: notmuch show requires at least one search term.\n"); return 1; @@ -1123,15 +1133,6 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } - /* if part was requested and format was not specified, use format=raw */ - if (params.part >= 0 && !format_specified) - format = &format_raw; - - /* If --format=raw specified without specifying part, we can only - * output single message, so set part=0 */ - if (params.raw && params.part < 0) - params.part = 0; - if (params.part >= 0) return do_show_single (ctx, query, format, ¶ms); else -- cgit v1.2.3 From 2c8959dad8863d185b237ed3e1f947fc46b32c98 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 6 Feb 2012 21:57:23 +0200 Subject: cli: reach previously unreachable cleanup code in "notmuch show" The last lines of notmuch_show_command() function were unreachable. Fix it by using a variable for return value. Signed-off-by: Jani Nikula --- notmuch-show.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 8f72e58..d930f94 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1029,7 +1029,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_database_t *notmuch; notmuch_query_t *query; char *query_string; - int opt_index; + int opt_index, ret; const notmuch_show_format_t *format = &format_text; notmuch_show_params_t params = { .part = -1 }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; @@ -1134,9 +1134,9 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } if (params.part >= 0) - return do_show_single (ctx, query, format, ¶ms); + ret = do_show_single (ctx, query, format, ¶ms); else - return do_show (ctx, query, format, ¶ms); + ret = do_show (ctx, query, format, ¶ms); notmuch_query_destroy (query); notmuch_database_close (notmuch); @@ -1144,5 +1144,5 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) if (params.cryptoctx) g_object_unref(params.cryptoctx); - return 0; + return ret; } -- cgit v1.2.3 From 5d021e52e2c39db224764e5e1d7f08b5e4068d77 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Tue, 7 Feb 2012 17:26:11 +0000 Subject: emacs: Ensure that gnupg output goes at the end of the buffer. When showing the user some details of gnupg output, ensure that those details are shown at the end of the gnupg status buffer ("*notmuch-crypto-gpg-out*"), otherwise it can end up mixed up with earlier output. --- emacs/notmuch-crypto.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 80ac350..c7ef1eb 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -129,6 +129,7 @@ mode." (window (display-buffer buffer t nil))) (with-selected-window window (with-current-buffer buffer + (goto-char (point-max)) (call-process "gpg" nil t t "--list-keys" fingerprint)) (recenter -1)))) @@ -139,6 +140,7 @@ mode." (window (display-buffer buffer t nil))) (with-selected-window window (with-current-buffer buffer + (goto-char (point-max)) (call-process "gpg" nil t t "--recv-keys" keyid) (insert "\n") (call-process "gpg" nil t t "--list-keys" keyid)) -- cgit v1.2.3 From 19ec74c50ebbf7f31997384e62b799fde99e7213 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:10 +0000 Subject: emacs: Rework crypto switch toggle. Re-work the existing crypto switch toggle to be based on a persistant buffer-local variable. To allow this, modify `notmuch-show-refresh-view' to erase and re-draw in the current buffer rather than killing the current buffer and creating a new one. (This will also allow more per-buffer behaviour in future patches.) Add a binding ('$') to toggle crypto processing of the current buffer and remove the prefix argument approach that achieves a similar result. --- emacs/notmuch-show.el | 113 ++++++++++++++++++++++++++------------------------ emacs/notmuch.el | 7 ++-- 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 24fde05..e209122 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -126,6 +126,22 @@ indentation." (const :tag "View interactively" notmuch-show-interactively-view-part))) +(defvar notmuch-show-thread-id nil) +(make-variable-buffer-local 'notmuch-show-thread-id) +(put 'notmuch-show-thread-id 'permanent-local t) + +(defvar notmuch-show-parent-buffer nil) +(make-variable-buffer-local 'notmuch-show-parent-buffer) +(put 'notmuch-show-parent-buffer 'permanent-local t) + +(defvar notmuch-show-query-context nil) +(make-variable-buffer-local 'notmuch-show-query-context) +(put 'notmuch-show-query-context 'permanent-local t) + +(defvar notmuch-show-process-crypto nil) +(make-variable-buffer-local 'notmuch-show-process-crypto) +(put 'notmuch-show-process-crypto 'permanent-local t) + (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion @@ -611,7 +627,7 @@ current buffer, if possible." (sigstatus (car (plist-get part :sigstatus)))) (notmuch-crypto-insert-sigstatus-button sigstatus from)) ;; if we're not adding sigstatus, tell the user how they can get it - (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts."))) + (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic MIME parts."))) (let ((inner-parts (plist-get part :content)) (start (point))) @@ -637,7 +653,7 @@ current buffer, if possible." (sigstatus (car (plist-get part :sigstatus)))) (notmuch-crypto-insert-sigstatus-button sigstatus from)))) ;; if we're not adding encstatus, tell the user how they can get it - (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts."))) + (button-put button 'help-echo "Set notmuch-crypto-process-mime to process cryptographic MIME parts."))) (let ((inner-parts (plist-get part :content)) (start (point))) @@ -764,8 +780,6 @@ current buffer, if possible." ;; Helper for parts which are generally not included in the default ;; JSON output. -;; Uses the buffer-local variable notmuch-show-process-crypto to -;; determine if parts should be decrypted first. (defun notmuch-show-get-bodypart-internal (message-id part-number) (let ((args '("show" "--format=raw")) (part-arg (format "--part=%s" part-number))) @@ -919,6 +933,15 @@ current buffer, if possible." ;; criteria. (notmuch-show-message-visible msg (plist-get msg :match)))) +(defun notmuch-show-toggle-process-crypto () + "Toggle the processing of cryptographic MIME parts." + (interactive) + (setq notmuch-show-process-crypto (not notmuch-show-process-crypto)) + (message (if notmuch-show-process-crypto + "Processing cryptographic MIME parts." + "Not processing cryptographic MIME parts.")) + (notmuch-show-refresh-view)) + (defun notmuch-show-insert-tree (tree depth) "Insert the message tree TREE at depth DEPTH in the current thread." (let ((msg (car tree)) @@ -934,15 +957,6 @@ current buffer, if possible." "Insert the forest of threads FOREST." (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest)) -(defvar notmuch-show-thread-id nil) -(make-variable-buffer-local 'notmuch-show-thread-id) -(defvar notmuch-show-parent-buffer nil) -(make-variable-buffer-local 'notmuch-show-parent-buffer) -(defvar notmuch-show-query-context nil) -(make-variable-buffer-local 'notmuch-show-query-context) -(defvar notmuch-show-buffer-name nil) -(make-variable-buffer-local 'notmuch-show-buffer-name) - (defun notmuch-show-buttonise-links (start end) "Buttonise URLs and mail addresses between START and END. @@ -962,7 +976,7 @@ a corresponding notmuch search." 'face goto-address-mail-face)))) ;;;###autoload -(defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name crypto-switch) +(defun notmuch-show (thread-id &optional parent-buffer query-context buffer-name) "Run \"notmuch show\" with the given thread ID and display results. The optional PARENT-BUFFER is the notmuch-search buffer from @@ -977,46 +991,41 @@ non-nil. The optional BUFFER-NAME provides the name of the buffer in which the message thread is shown. If it is nil (which occurs when the command is called interactively) the argument to the -function is used. - -The optional CRYPTO-SWITCH toggles the value of the -notmuch-crypto-process-mime customization variable for this show -buffer." +function is used." (interactive "sNotmuch show: ") - (let* ((process-crypto (if crypto-switch - (not notmuch-crypto-process-mime) - notmuch-crypto-process-mime))) - (notmuch-show-worker thread-id parent-buffer query-context buffer-name process-crypto))) - -(defun notmuch-show-worker (thread-id parent-buffer query-context buffer-name process-crypto) - (let* ((buffer-name (generate-new-buffer-name - (or buffer-name - (concat "*notmuch-" thread-id "*")))) - (buffer (get-buffer-create buffer-name)) - (inhibit-read-only t)) - (switch-to-buffer buffer) + (let ((buffer-name (generate-new-buffer-name + (or buffer-name + (concat "*notmuch-" thread-id "*"))))) + (switch-to-buffer (get-buffer-create buffer-name)) + ;; Set the default value for `notmuch-show-process-crypto' in this + ;; buffer. + (setq notmuch-show-process-crypto notmuch-crypto-process-mime) + + (setq notmuch-show-thread-id thread-id + notmuch-show-parent-buffer parent-buffer + notmuch-show-query-context query-context) + (notmuch-show-worker))) + +(defun notmuch-show-worker () + (let ((inhibit-read-only t)) + (notmuch-show-mode) ;; Don't track undo information for this buffer (set 'buffer-undo-list t) - (setq notmuch-show-thread-id thread-id) - (setq notmuch-show-parent-buffer parent-buffer) - (setq notmuch-show-query-context query-context) - (setq notmuch-show-buffer-name buffer-name) - (setq notmuch-show-process-crypto process-crypto) - (erase-buffer) (goto-char (point-min)) (save-excursion - (let* ((basic-args (list thread-id)) - (args (if query-context - (append (list "\'") basic-args (list "and (" query-context ")\'")) + (let* ((basic-args (list notmuch-show-thread-id)) + (args (if notmuch-show-query-context + (append (list "\'") basic-args + (list "and (" notmuch-show-query-context ")\'")) (append (list "\'") basic-args (list "\'"))))) (notmuch-show-insert-forest (notmuch-query-get-threads args)) ;; If the query context reduced the results to nothing, run ;; the basic query. (when (and (eq (buffer-size) 0) - query-context) + notmuch-show-query-context) (notmuch-show-insert-forest (notmuch-query-get-threads basic-args)))) @@ -1033,21 +1042,14 @@ buffer." (notmuch-show-mark-read))) -(defun notmuch-show-refresh-view (&optional crypto-switch) - "Refresh the current view (with crypto switch if prefix given). +(defun notmuch-show-refresh-view () + "Refresh the current view. -Kills the current buffer and reruns notmuch show with the same -thread id. If a prefix is given, crypto processing is toggled." - (interactive "P") - (let ((thread-id notmuch-show-thread-id) - (parent-buffer notmuch-show-parent-buffer) - (query-context notmuch-show-query-context) - (buffer-name notmuch-show-buffer-name) - (process-crypto (if crypto-switch - (not notmuch-show-process-crypto) - notmuch-show-process-crypto))) - (notmuch-kill-this-buffer) - (notmuch-show-worker thread-id parent-buffer query-context buffer-name process-crypto))) +Refreshes the current view, observing changes in cryptographic preferences." + (interactive) + (let ((inhibit-read-only t)) + (erase-buffer)) + (notmuch-show-worker)) (defvar notmuch-show-stash-map (let ((map (make-sparse-keymap))) @@ -1100,6 +1102,7 @@ thread id. If a prefix is given, crypto processing is toggled." (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all) (define-key map (kbd "RET") 'notmuch-show-toggle-message) (define-key map "#" 'notmuch-show-print-message) + (define-key map "$" 'notmuch-show-toggle-process-crypto) map) "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 11113fe..5b4f1c5 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -494,9 +494,9 @@ Complete list of currently available key bindings: "Return a list of authors for the current region" (notmuch-search-properties-in-region 'notmuch-search-subject beg end)) -(defun notmuch-search-show-thread (&optional crypto-switch) +(defun notmuch-search-show-thread () "Display the currently selected thread." - (interactive "P") + (interactive) (let ((thread-id (notmuch-search-find-thread-id)) (subject (notmuch-prettify-subject (notmuch-search-find-subject)))) (if (> (length thread-id) 0) @@ -504,8 +504,7 @@ Complete list of currently available key bindings: (current-buffer) notmuch-search-query-string ;; Name the buffer based on the subject. - (concat "*" (truncate-string-to-width subject 30 nil nil t) "*") - crypto-switch) + (concat "*" (truncate-string-to-width subject 30 nil nil t) "*")) (message "End of search results.")))) (defun notmuch-search-reply-to-thread (&optional prompt-for-sender) -- cgit v1.2.3 From 44a544ede0ae9540aabe5833efd26a869b8a9d15 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:12 +0000 Subject: emacs: Allow `notmuch-show-mode' to display only matching messages. The current behaviour (all messages shown, non-matching collapsed) is retained as the default. Type '!' to switch to showing only the matching messages - non-matching messages are not available. '!' will switch back to showing everything. --- emacs/notmuch-show.el | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index e209122..c8371e7 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -142,6 +142,10 @@ indentation." (make-variable-buffer-local 'notmuch-show-process-crypto) (put 'notmuch-show-process-crypto 'permanent-local t) +(defvar notmuch-show-elide-non-matching-messages nil) +(make-variable-buffer-local 'notmuch-show-elide-non-matching-messages) +(put 'notmuch-show-elide-non-matching-messages 'permanent-local t) + (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion @@ -942,11 +946,22 @@ current buffer, if possible." "Not processing cryptographic MIME parts.")) (notmuch-show-refresh-view)) +(defun notmuch-show-toggle-elide-non-matching () + "Toggle the display of non-matching messages." + (interactive) + (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages)) + (message (if notmuch-show-elide-non-matching-messages + "Showing matching messages only." + "Showing all messages.")) + (notmuch-show-refresh-view)) + (defun notmuch-show-insert-tree (tree depth) "Insert the message tree TREE at depth DEPTH in the current thread." (let ((msg (car tree)) (replies (cadr tree))) - (notmuch-show-insert-msg msg depth) + (if (or (not notmuch-show-elide-non-matching-messages) + (plist-get msg :match)) + (notmuch-show-insert-msg msg depth)) (notmuch-show-insert-thread replies (1+ depth)))) (defun notmuch-show-insert-thread (thread depth) @@ -1102,6 +1117,7 @@ Refreshes the current view, observing changes in cryptographic preferences." (define-key map (kbd "M-RET") 'notmuch-show-open-or-close-all) (define-key map (kbd "RET") 'notmuch-show-toggle-message) (define-key map "#" 'notmuch-show-print-message) + (define-key map "!" 'notmuch-show-toggle-elide-non-matching) (define-key map "$" 'notmuch-show-toggle-process-crypto) map) "Keymap for \"notmuch show\" buffers.") -- cgit v1.2.3 From c205e8ffaefbc7c28e0ac48108fa4d1b69c1f104 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:14 +0000 Subject: emacs: Allow the indentation of content to be toggled. Very deeply indented content is sometimes difficult to read (particular for something like patches). Allow the indentation of the content to be toggled with '<'. Indentation of the header lines is not affected, so it remains possible to see the structure of the thread. --- emacs/notmuch-show.el | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index c8371e7..60fd217 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -146,6 +146,10 @@ indentation." (make-variable-buffer-local 'notmuch-show-elide-non-matching-messages) (put 'notmuch-show-elide-non-matching-messages 'permanent-local t) +(defvar notmuch-show-indent-content t) +(make-variable-buffer-local 'notmuch-show-indent-content) +(put 'notmuch-show-indent-content 'permanent-local t) + (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion @@ -256,10 +260,12 @@ operation on the contents of the current buffer." (all (buffer-substring (notmuch-show-message-top) (notmuch-show-message-bottom))) - (props (notmuch-show-get-message-properties))) + (props (notmuch-show-get-message-properties)) + (indenting notmuch-show-indent-content)) (with-temp-buffer (insert all) - (indent-rigidly (point-min) (point-max) (- depth)) + (if indenting + (indent-rigidly (point-min) (point-max) (- depth))) ;; Remove the original header. (goto-char (point-min)) (re-search-forward "^$" (point-max) nil) @@ -900,7 +906,8 @@ current buffer, if possible." (setq body-start (point-marker)) ;; A blank line between the headers and the body. (insert "\n") - (notmuch-show-insert-body msg (plist-get msg :body) depth) + (notmuch-show-insert-body msg (plist-get msg :body) + (if notmuch-show-indent-content depth 0)) ;; Ensure that the body ends with a newline. (unless (bolp) (insert "\n")) @@ -908,7 +915,8 @@ current buffer, if possible." (setq content-end (point-marker)) ;; Indent according to the depth in the thread. - (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth)) + (if notmuch-show-indent-content + (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth))) (setq message-end (point-max-marker)) @@ -955,6 +963,15 @@ current buffer, if possible." "Showing all messages.")) (notmuch-show-refresh-view)) +(defun notmuch-show-toggle-thread-indentation () + "Toggle the indentation of threads." + (interactive) + (setq notmuch-show-indent-content (not notmuch-show-indent-content)) + (message (if notmuch-show-indent-content + "Content is indented." + "Content is not indented.")) + (notmuch-show-refresh-view)) + (defun notmuch-show-insert-tree (tree depth) "Insert the message tree TREE at depth DEPTH in the current thread." (let ((msg (car tree)) @@ -1119,6 +1136,7 @@ Refreshes the current view, observing changes in cryptographic preferences." (define-key map "#" 'notmuch-show-print-message) (define-key map "!" 'notmuch-show-toggle-elide-non-matching) (define-key map "$" 'notmuch-show-toggle-process-crypto) + (define-key map "<" 'notmuch-show-toggle-thread-indentation) map) "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) -- cgit v1.2.3 From 7bcab5d645b166a4ef484b9b3af33e341660ae92 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:15 +0000 Subject: emacs: Add a binding (t) to toggle the truncation of long lines. --- emacs/notmuch-show.el | 1 + 1 file changed, 1 insertion(+) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 60fd217..533cfa9 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1137,6 +1137,7 @@ Refreshes the current view, observing changes in cryptographic preferences." (define-key map "!" 'notmuch-show-toggle-elide-non-matching) (define-key map "$" 'notmuch-show-toggle-process-crypto) (define-key map "<" 'notmuch-show-toggle-thread-indentation) + (define-key map "t" 'toggle-truncate-lines) map) "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) -- cgit v1.2.3 From 48766fca71502fc5ff0cfc594758b6d2af47cb9d Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:16 +0000 Subject: emacs: Optionally retain the state of the buffer during `notmuch-show-refresh-view'. With an argument, record and reply the state of the buffer during `notmuch-show-refresh-view'. In this context, "state" is defined as: - the open/closed state of each message, - the current message. Traditional use of refresh with the = key does not retain the state. The recently introduced toggle commands ($, !, < and >) do retain the state. --- emacs/notmuch-show.el | 56 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 533cfa9..33845d3 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -952,7 +952,7 @@ current buffer, if possible." (message (if notmuch-show-process-crypto "Processing cryptographic MIME parts." "Not processing cryptographic MIME parts.")) - (notmuch-show-refresh-view)) + (notmuch-show-refresh-view t)) (defun notmuch-show-toggle-elide-non-matching () "Toggle the display of non-matching messages." @@ -961,7 +961,7 @@ current buffer, if possible." (message (if notmuch-show-elide-non-matching-messages "Showing matching messages only." "Showing all messages.")) - (notmuch-show-refresh-view)) + (notmuch-show-refresh-view t)) (defun notmuch-show-toggle-thread-indentation () "Toggle the indentation of threads." @@ -970,7 +970,7 @@ current buffer, if possible." (message (if notmuch-show-indent-content "Content is indented." "Content is not indented.")) - (notmuch-show-refresh-view)) + (notmuch-show-refresh-view t)) (defun notmuch-show-insert-tree (tree depth) "Insert the message tree TREE at depth DEPTH in the current thread." @@ -1074,14 +1074,54 @@ function is used." (notmuch-show-mark-read))) -(defun notmuch-show-refresh-view () +(defun notmuch-show-capture-state () + "Capture the state of the current buffer. + +This includes: + - the list of open messages, + - the current message." + (list (notmuch-show-get-message-id) (notmuch-show-get-message-ids-for-open-messages))) + +(defun notmuch-show-apply-state (state) + "Apply STATE to the current buffer. + +This includes: + - opening the messages previously opened, + - closing all other messages, + - moving to the correct current message." + (let ((current (car state)) + (open (cadr state))) + + ;; Open those that were open. + (goto-char (point-min)) + (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties) + (member (notmuch-show-get-message-id) open)) + until (not (notmuch-show-goto-message-next))) + + ;; Go to the previously open message. + (goto-char (point-min)) + (unless (loop if (string= current (notmuch-show-get-message-id)) + return t + until (not (notmuch-show-goto-message-next))) + (goto-char (point-min)) + (message "Previously current message not found.")) + (notmuch-show-message-adjust))) + +(defun notmuch-show-refresh-view (&optional retain-state) "Refresh the current view. -Refreshes the current view, observing changes in cryptographic preferences." +Refreshes the current view, observing changes in display +preferences. If RETAIN-STATE is non-nil then the state of the +buffer is stored and re-applied after the refresh." (interactive) - (let ((inhibit-read-only t)) - (erase-buffer)) - (notmuch-show-worker)) + (let ((inhibit-read-only t) + state) + (if retain-state + (setq state (notmuch-show-capture-state))) + (erase-buffer) + (notmuch-show-worker) + (if state + (notmuch-show-apply-state state)))) (defvar notmuch-show-stash-map (let ((map (make-sparse-keymap))) -- cgit v1.2.3 From d2684228847715d7616ef39a2744e84989fb05bd Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:17 +0000 Subject: emacs: Check that the parent buffer is alive before using it. --- emacs/notmuch-show.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 33845d3..0b023a5 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1704,7 +1704,7 @@ added." (interactive "P") (let ((parent-buffer notmuch-show-parent-buffer)) (notmuch-kill-this-buffer) - (when parent-buffer + (when (buffer-live-p parent-buffer) (switch-to-buffer parent-buffer) (notmuch-search-next-thread) (if show-next -- cgit v1.2.3 From 866ce8b132c4b1e912f21a8b054f839f53281dde Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:18 +0000 Subject: emacs: Add `notmuch-show-only-matching-messages'. Allow the user to choose that only matching messages are shown by default. --- emacs/notmuch-show.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 0b023a5..7ffa1ed 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -126,6 +126,11 @@ indentation." (const :tag "View interactively" notmuch-show-interactively-view-part))) +(defcustom notmuch-show-only-matching-messages nil + "Only matching messages are shown by default." + :type 'boolean + :group 'notmuch-show) + (defvar notmuch-show-thread-id nil) (make-variable-buffer-local 'notmuch-show-thread-id) (put 'notmuch-show-thread-id 'permanent-local t) @@ -1032,6 +1037,9 @@ function is used." ;; Set the default value for `notmuch-show-process-crypto' in this ;; buffer. (setq notmuch-show-process-crypto notmuch-crypto-process-mime) + ;; Set the default value for + ;; `notmuch-show-elide-non-matching-messages' in this buffer. + (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages) (setq notmuch-show-thread-id thread-id notmuch-show-parent-buffer parent-buffer -- cgit v1.2.3 From 668b66ec85a649255441f8ab1a6ee5baa680b4e4 Mon Sep 17 00:00:00 2001 From: David Edmondson Date: Wed, 8 Feb 2012 08:02:19 +0000 Subject: emacs: A prefix argument to `notmuch-show' should invert the matching message behaviour. Allow the user to open a thread with inverted `notmuch-show-only-matching-messages' behaviour using a prefix argument. --- emacs/notmuch-show.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 7ffa1ed..284e9cd 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1038,8 +1038,11 @@ function is used." ;; buffer. (setq notmuch-show-process-crypto notmuch-crypto-process-mime) ;; Set the default value for - ;; `notmuch-show-elide-non-matching-messages' in this buffer. + ;; `notmuch-show-elide-non-matching-messages' in this buffer. If + ;; there is a prefix argument, invert the default. (setq notmuch-show-elide-non-matching-messages notmuch-show-only-matching-messages) + (if current-prefix-arg + (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages))) (setq notmuch-show-thread-id thread-id notmuch-show-parent-buffer parent-buffer -- cgit v1.2.3 From a5674c21584a32a4f8a8a3e9ea3c3576f772e744 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Wed, 8 Feb 2012 20:48:51 +0400 Subject: emacs: cleanup and simplify `notmuch-show-archive-thread' and related functions Recent changes in notmuch-show tagging introduced some code duplication. The patch cleanups and simplifies `notmuch-show-archive-thread' function by using `notmuch-show-tag-all', no longer used function are removed. After the change, `notmuch-show-archive-thread' function becomes symmetric with `notmuch-show-archive-message'. A side effect of these changes is that `notmuch-show-archive-thread' no longer calls "notmuch tag" for each message in the thread. --- emacs/notmuch-show.el | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 284e9cd..43408d9 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1690,26 +1690,6 @@ argument, hide all of the messages." (interactive) (backward-button 1)) -(defun notmuch-show-tag-thread-internal (tag &optional remove) - "Add tag to the current set of messages. - -If the remove switch is given, tags will be removed instead of -added." - (goto-char (point-min)) - (let ((op (if remove "-" "+"))) - (loop do (notmuch-show-tag-message (concat op tag)) - until (not (notmuch-show-goto-message-next))))) - -(defun notmuch-show-add-tag-thread (tag) - "Add tag to all messages in the current thread." - (interactive) - (notmuch-show-tag-thread-internal tag)) - -(defun notmuch-show-remove-tag-thread (tag) - "Remove tag from all messages in the current thread." - (interactive) - (notmuch-show-tag-thread-internal tag t)) - (defun notmuch-show-next-thread (&optional show-next) "Move to the next item in the search results, if any." (interactive "P") @@ -1737,9 +1717,8 @@ being delivered to the same thread. It does not archive the entire thread, but only the messages shown in the current buffer." (interactive "P") - (if unarchive - (notmuch-show-add-tag-thread "inbox") - (notmuch-show-remove-tag-thread "inbox"))) + (let ((op (if unarchive "+" "-"))) + (notmuch-show-tag-all (concat op "inbox")))) (defun notmuch-show-archive-thread-then-next () "Archive each message in thread, then show next thread from search." -- cgit v1.2.3 From d8bff4b3af412cd56283e6e2cda256b79d2fd40b Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Mon, 13 Feb 2012 15:09:07 +0400 Subject: emacs: allow to set RETAIN-STATE for `notmuch-show-refresh-view' interactively The notmuch-show view refresh function (`notmuch-show-refresh-view', bound to "=") accepts an optional RETAIN-STATE argument. The patch allows to set this argument interactively by using "C-u =". --- emacs/notmuch-show.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 43408d9..aa9ccee 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1124,7 +1124,7 @@ This includes: Refreshes the current view, observing changes in display preferences. If RETAIN-STATE is non-nil then the state of the buffer is stored and re-applied after the refresh." - (interactive) + (interactive "P") (let ((inhibit-read-only t) state) (if retain-state -- cgit v1.2.3 From 5f39979a4aa2aea7d587201702fa3443248334bd Mon Sep 17 00:00:00 2001 From: Ethan Glasser-Camp Date: Tue, 7 Feb 2012 05:05:03 -0500 Subject: Free the results of scandir() scandir() returns "strings allocated via malloc(3)" which are then "collected in array namelist which is allocated via malloc(3)". Currently we just free the array namelist. Instead, free all the entries of namelist, and then free namelist. entry only points to elements of namelist, so we don't free it separately. --- notmuch-new.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/notmuch-new.c b/notmuch-new.c index a569a54..8dbebb3 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -559,12 +559,14 @@ add_files_recursive (notmuch_database_t *notmuch, DONE: if (next) talloc_free (next); - if (entry) - free (entry); if (dir) closedir (dir); - if (fs_entries) + if (fs_entries) { + for (i = 0; i < num_fs_entries; i++) + free (fs_entries[i]); + free (fs_entries); + } if (db_subdirs) notmuch_filenames_destroy (db_subdirs); if (db_files) @@ -704,10 +706,12 @@ count_files (const char *path, int *count) } DONE: - if (entry) - free (entry); - if (fs_entries) + if (fs_entries) { + for (i = 0; i < num_fs_entries; i++) + free (fs_entries[i]); + free (fs_entries); + } } static void -- cgit v1.2.3 From b2734519db78fdec76eeafc5fe8f5631a6436cf6 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 15 Feb 2012 22:25:13 +0100 Subject: python: provide a Database.close function Rename Database.__del__ to Database.close, move it just below the open function and call close() in a newly created destructor just below the constructor. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 36b65ec..d0e38dd 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -142,6 +142,9 @@ class Database(object): else: self.create(path) + def __del__(self): + self.close() + def _assert_db_is_initialized(self): """Raises :exc:`NotInitializedError` if self._db is `None`""" if self._db is None: @@ -193,6 +196,16 @@ class Database(object): raise NotmuchError(message="Could not open the specified database") self._db = res + _close = nmlib.notmuch_database_close + _close.argtypes = [NotmuchDatabaseP] + _close.restype = None + + def close(self): + """Close and free the notmuch database if needed""" + if self._db is not None: + self._close(self._db) + self._db = None + def get_path(self): """Returns the file path of an open database""" self._assert_db_is_initialized() @@ -531,15 +544,6 @@ class Database(object): def __repr__(self): return "'Notmuch DB " + self.get_path() + "'" - _close = nmlib.notmuch_database_close - _close.argtypes = [NotmuchDatabaseP] - _close.restype = None - - def __del__(self): - """Close and free the notmuch database if needed""" - if self._db is not None: - self._close(self._db) - def _get_user_default_db(self): """ Reads a user's notmuch config and returns his db location -- cgit v1.2.3 From 36ce7e3c989f6f66a4b250483de3b45e902d68d1 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 15 Feb 2012 22:41:16 +0100 Subject: python: implement the context manager protocol for database objects Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index d0e38dd..533948c 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -41,6 +41,10 @@ class Database(object): :exc:`XapianError` as the underlying database has been modified. Close and reopen the database to continue working with it. + :class:`Database` objects implement the context manager protocol + so you can use the :keyword:`with` statement to ensure that the + database is properly closed. + .. note:: Any function in this class can and will throw an @@ -206,6 +210,18 @@ class Database(object): self._close(self._db) self._db = None + def __enter__(self): + ''' + Implements the context manager protocol. + ''' + return self + + def __exit__(self, exc_type, exc_value, traceback): + ''' + Implements the context manager protocol. + ''' + self.close() + def get_path(self): """Returns the file path of an open database""" self._assert_db_is_initialized() -- cgit v1.2.3 From e4ceb19cc73766e3c0becc537c36679785b215a6 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Tue, 14 Feb 2012 18:09:47 +0100 Subject: emacs: Fix display of highlighted line in notmuch-search When notmuch-search-line-faces is used to set background color in search results, the highlight of the current line is not always displayed correctly. This patch fixes that by increasing the priority property of the highlight overlay. --- emacs/notmuch.el | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 5b4f1c5..f851c6f 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -249,10 +249,17 @@ For a mouse binding, return nil." (set-buffer-modified-p nil) (view-buffer (current-buffer) 'kill-buffer-if-not-modified)))) -(defcustom notmuch-search-hook '(hl-line-mode) +(require 'hl-line) + +(defun notmuch-hl-line-mode () + (prog1 (hl-line-mode) + (when hl-line-overlay + (overlay-put hl-line-overlay 'priority 1)))) + +(defcustom notmuch-search-hook '(notmuch-hl-line-mode) "List of functions to call when notmuch displays the search results." :type 'hook - :options '(hl-line-mode) + :options '(notmuch-hl-line-mode) :group 'notmuch-search :group 'notmuch-hooks) @@ -567,7 +574,7 @@ a list of strings of the form \"+TAG\" or \"-TAG\". the messages that are about to be tagged" :type 'hook - :options '(hl-line-mode) + :options '(notmuch-hl-line-mode) :group 'notmuch-hooks) (defcustom notmuch-after-tag-hook nil @@ -578,7 +585,7 @@ a list of strings of the form \"+TAG\" or \"-TAG\". 'query' will be a string containing the search query that determines the messages that were tagged" :type 'hook - :options '(hl-line-mode) + :options '(notmuch-hl-line-mode) :group 'notmuch-hooks) (defun notmuch-search-set-tags (tags) -- cgit v1.2.3 From 863c1495141084dd1e66a73be35560b2531b71bf Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 15 Feb 2012 11:17:30 +0200 Subject: test: add tests wrt ignoring user-specified files and directories Files and directories which are specified in 'new.ignore' in the config file shouldn't be indexed nor reported by `notmuch new'. This is basically Pieter's work with Austin's comments addressed. --- test/new | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/new b/test/new index 49f390d..e453684 100755 --- a/test/new +++ b/test/new @@ -153,4 +153,26 @@ rm -rf "${MAIL_DIR}"/two output=$(NOTMUCH_NEW) test_expect_equal "$output" "No new mail. Removed 3 messages." +# This test requires that notmuch new has been run at least once. +test_begin_subtest "Skip and report non-mail files" +generate_message +mkdir -p "${MAIL_DIR}"/.git && touch "${MAIL_DIR}"/.git/config +touch "${MAIL_DIR}"/ignored_file +touch "${MAIL_DIR}"/.ignored_hidden_file +output=$(NOTMUCH_NEW 2>&1) +test_expect_equal "$output" \ +"Note: Ignoring non-mail file: ${MAIL_DIR}/.git/config +Note: Ignoring non-mail file: ${MAIL_DIR}/.ignored_hidden_file +Note: Ignoring non-mail file: ${MAIL_DIR}/ignored_file +Added 1 new message to the database." + +test_begin_subtest "Ignore files and directories specified in new.ignore" +test_subtest_known_broken +generate_message +notmuch config set new.ignore .git ignored_file .ignored_hidden_file +touch "${MAIL_DIR}"/.git # change .git's mtime for notmuch new to rescan. +output=$(NOTMUCH_NEW 2>&1) +test_expect_equal "$output" "Added 1 new message to the database." + + test_done -- cgit v1.2.3 From ce1e720de64270a7cbb4bc3fba2c7fe081de3edc Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 15 Feb 2012 11:17:31 +0200 Subject: add support for user-specified files & directories to ignore A new configuration key 'new.ignore' is used to determine which files and directories user wants not to be scanned as new mails. Mark the corresponding test as no longer broken. This work merges my previous attempts and Andreas Amann's work in id:"ylp7hi23mw8.fsf@tyndall.ie" --- notmuch-client.h | 9 +++++++++ notmuch-config.c | 30 +++++++++++++++++++++++++++++- notmuch-new.c | 45 +++++++++++++++++++++++++++++++++------------ test/new | 1 - 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 60828aa..f4a62cc 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -250,6 +250,15 @@ notmuch_config_set_new_tags (notmuch_config_t *config, const char *new_tags[], size_t length); +const char ** +notmuch_config_get_new_ignore (notmuch_config_t *config, + size_t *length); + +void +notmuch_config_set_new_ignore (notmuch_config_t *config, + const char *new_ignore[], + size_t length); + notmuch_bool_t notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config); diff --git a/notmuch-config.c b/notmuch-config.c index a124e34..1f01128 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -44,7 +44,10 @@ static const char new_config_comment[] = " The following options are supported here:\n" "\n" "\ttags A list (separated by ';') of the tags that will be\n" - "\t added to all messages incorporated by \"notmuch new\".\n"; + "\t added to all messages incorporated by \"notmuch new\".\n" + "\n" + "\tignore A list (separated by ';') of file and directory names\n" + "\t that will not be searched for messages by \"notmuch new\".\n"; static const char user_config_comment[] = " User configuration\n" @@ -105,6 +108,8 @@ struct _notmuch_config { size_t user_other_email_length; const char **new_tags; size_t new_tags_length; + const char **new_ignore; + size_t new_ignore_length; notmuch_bool_t maildir_synchronize_flags; const char **search_exclude_tags; size_t search_exclude_tags_length; @@ -264,6 +269,8 @@ notmuch_config_open (void *ctx, config->user_other_email_length = 0; config->new_tags = NULL; config->new_tags_length = 0; + config->new_ignore = NULL; + config->new_ignore_length = 0; config->maildir_synchronize_flags = TRUE; config->search_exclude_tags = NULL; config->search_exclude_tags_length = 0; @@ -361,6 +368,10 @@ notmuch_config_open (void *ctx, notmuch_config_set_new_tags (config, tags, 2); } + if (notmuch_config_get_new_ignore (config, &tmp) == NULL) { + notmuch_config_set_new_ignore (config, NULL, 0); + } + if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) { if (is_new) { const char *tags[] = { "deleted", "spam" }; @@ -609,6 +620,14 @@ notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length) &(config->new_tags_length), length); } +const char ** +notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length) +{ + return _config_get_list (config, "new", "ignore", + &(config->new_ignore), + &(config->new_ignore_length), length); +} + void notmuch_config_set_user_other_email (notmuch_config_t *config, const char *list[], @@ -627,6 +646,15 @@ notmuch_config_set_new_tags (notmuch_config_t *config, &(config->new_tags)); } +void +notmuch_config_set_new_ignore (notmuch_config_t *config, + const char *list[], + size_t length) +{ + _config_set_list (config, "new", "ignore", list, length, + &(config->new_ignore)); +} + const char ** notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length) { diff --git a/notmuch-new.c b/notmuch-new.c index 8dbebb3..4f13535 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -39,6 +39,8 @@ typedef struct { int verbose; const char **new_tags; size_t new_tags_length; + const char **new_ignore; + size_t new_ignore_length; int total_files; int processed_files; @@ -181,6 +183,20 @@ _entries_resemble_maildir (struct dirent **entries, int count) return 0; } +/* Test if the file/directory is to be ignored. + */ +static notmuch_bool_t +_entry_in_ignore_list (const char *entry, add_files_state_t *state) +{ + size_t i; + + for (i = 0; i < state->new_ignore_length; i++) + if (strcmp (entry, state->new_ignore[i]) == 0) + return TRUE; + + return FALSE; +} + /* Examine 'path' recursively as follows: * * o Ask the filesystem for the mtime of 'path' (fs_mtime) @@ -320,15 +336,15 @@ add_files_recursive (notmuch_database_t *notmuch, } /* Ignore special directories to avoid infinite recursion. - * Also ignore the .notmuch directory and any "tmp" directory - * that appears within a maildir. + * Also ignore the .notmuch directory, any "tmp" directory + * that appears within a maildir and files/directories + * the user has configured to be ignored. */ - /* XXX: Eventually we'll want more sophistication to let the - * user specify files to be ignored. */ if (strcmp (entry->d_name, ".") == 0 || strcmp (entry->d_name, "..") == 0 || (is_maildir && strcmp (entry->d_name, "tmp") == 0) || - strcmp (entry->d_name, ".notmuch") ==0) + strcmp (entry->d_name, ".notmuch") == 0 || + _entry_in_ignore_list (entry->d_name, state)) { continue; } @@ -369,6 +385,10 @@ add_files_recursive (notmuch_database_t *notmuch, entry = fs_entries[i]; + /* Ignore files & directories user has configured to be ignored */ + if (_entry_in_ignore_list (entry->d_name, state)) + continue; + /* Check if we've walked past any names in db_files or * db_subdirs. If so, these have been deleted. */ while (notmuch_filenames_valid (db_files) && @@ -650,7 +670,7 @@ add_files (notmuch_database_t *notmuch, * initialized to zero by the top-level caller before calling * count_files). */ static void -count_files (const char *path, int *count) +count_files (const char *path, int *count, add_files_state_t *state) { struct dirent *entry = NULL; char *next; @@ -672,13 +692,13 @@ count_files (const char *path, int *count) entry = fs_entries[i++]; /* Ignore special directories to avoid infinite recursion. - * Also ignore the .notmuch directory. + * Also ignore the .notmuch directory and files/directories + * the user has configured to be ignored. */ - /* XXX: Eventually we'll want more sophistication to let the - * user specify files to be ignored. */ if (strcmp (entry->d_name, ".") == 0 || strcmp (entry->d_name, "..") == 0 || - strcmp (entry->d_name, ".notmuch") == 0) + strcmp (entry->d_name, ".notmuch") == 0 || + _entry_in_ignore_list (entry->d_name, state)) { continue; } @@ -699,7 +719,7 @@ count_files (const char *path, int *count) fflush (stdout); } } else if (S_ISDIR (st.st_mode)) { - count_files (next, count); + count_files (next, count, state); } free (next); @@ -841,6 +861,7 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) return 1; add_files_state.new_tags = notmuch_config_get_new_tags (config, &add_files_state.new_tags_length); + add_files_state.new_ignore = notmuch_config_get_new_ignore (config, &add_files_state.new_ignore_length); add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config); db_path = notmuch_config_get_database_path (config); @@ -856,7 +877,7 @@ notmuch_new_command (void *ctx, int argc, char *argv[]) int count; count = 0; - count_files (db_path, &count); + count_files (db_path, &count, &add_files_state); if (interrupted) return 1; diff --git a/test/new b/test/new index e453684..79a6c97 100755 --- a/test/new +++ b/test/new @@ -167,7 +167,6 @@ Note: Ignoring non-mail file: ${MAIL_DIR}/ignored_file Added 1 new message to the database." test_begin_subtest "Ignore files and directories specified in new.ignore" -test_subtest_known_broken generate_message notmuch config set new.ignore .git ignored_file .ignored_hidden_file touch "${MAIL_DIR}"/.git # change .git's mtime for notmuch new to rescan. -- cgit v1.2.3 From 4c31cc615b844fc792367a092daac0b3cafe9e49 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 15 Feb 2012 11:17:32 +0200 Subject: NEWS: add news section for new.ignore Added NEWS section 'Mail store folder/file ignore'. --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index f449fba..e9abb86 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,12 @@ Tag exclusion notmuch config set search.exclude_tags deleted spam +Mail store folder/file ignore + + A new configuration option, `new.ignore`, lets users specify a + ;-separated list of file and directory names that will not be + searched for messages by "notmuch new". + Emacs Interface --------------- -- cgit v1.2.3 From c471d448379b13d8133dfc73b7bbec43964f26d2 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 00:57:59 +0100 Subject: python: remove unused but imported symbol Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/filename.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index 3f54104..469b6a5 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -18,7 +18,7 @@ Copyright 2010 Sebastian Spaeth ' """ from ctypes import c_char_p from notmuch.globals import (nmlib, STATUS, NotmuchError, - NotmuchFilenamesP, NotmuchMessageP, _str, Python3StringMixIn) + NotmuchFilenamesP, NotmuchMessageP, Python3StringMixIn) class Filenames(Python3StringMixIn): -- cgit v1.2.3 From ff287531ca765f39def6941cd13eb77aa36c14fa Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 01:01:33 +0100 Subject: python: remove unused but imported symbol Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/tag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index 71d81dd..d2dc498 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -17,7 +17,7 @@ along with notmuch. If not, see . Copyright 2010 Sebastian Spaeth ' """ from ctypes import c_char_p -from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP, _str, Python3StringMixIn +from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP, Python3StringMixIn class Tags(Python3StringMixIn): -- cgit v1.2.3 From 5d69d272c3025f537de0e9995dee394c75ed1005 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 01:10:45 +0100 Subject: python: move Query class to its own file Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 181 +++------------------------------ bindings/python/notmuch/query.py | 192 ++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 166 deletions(-) create mode 100644 bindings/python/notmuch/query.py diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 533948c..0958ce0 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -20,14 +20,22 @@ Copyright 2010 Sebastian Spaeth ' import os import codecs from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER -from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError, - NullPointerError, Enum, _str, - NotmuchDatabaseP, NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP, - NotmuchQueryP, NotmuchMessagesP, NotmuchThreadsP, NotmuchFilenamesP) -from notmuch.thread import Threads -from notmuch.message import Messages, Message +from notmuch.globals import ( + nmlib, + STATUS, + NotmuchError, + NotInitializedError, + Enum, + _str, + NotmuchDatabaseP, + NotmuchDirectoryP, + NotmuchMessageP, + NotmuchTagsP, + NotmuchFilenamesP +) +from notmuch.message import Message from notmuch.tag import Tags - +from .query import Query class Database(object): """The :class:`Database` is the highest-level object that notmuch @@ -590,165 +598,6 @@ class Database(object): return self._db -class Query(object): - """Represents a search query on an opened :class:`Database`. - - A query selects and filters a subset of messages from the notmuch - database we derive from. - - :class:`Query` provides an instance attribute :attr:`sort`, which - contains the sort order (if specified via :meth:`set_sort`) or - `None`. - - Any function in this class may throw an :exc:`NotInitializedError` - in case the underlying query object was not set up correctly. - - .. note:: Do remember that as soon as we tear down this object, - all underlying derived objects such as threads, - messages, tags etc will be freed by the underlying library - as well. Accessing these objects will lead to segfaults and - other unexpected behavior. See above for more details. - """ - # constants - SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED']) - """Constants: Sort order in which to return results""" - - """notmuch_query_create""" - _create = nmlib.notmuch_query_create - _create.argtypes = [NotmuchDatabaseP, c_char_p] - _create.restype = NotmuchQueryP - - """notmuch_query_search_threads""" - _search_threads = nmlib.notmuch_query_search_threads - _search_threads.argtypes = [NotmuchQueryP] - _search_threads.restype = NotmuchThreadsP - - """notmuch_query_search_messages""" - _search_messages = nmlib.notmuch_query_search_messages - _search_messages.argtypes = [NotmuchQueryP] - _search_messages.restype = NotmuchMessagesP - - """notmuch_query_count_messages""" - _count_messages = nmlib.notmuch_query_count_messages - _count_messages.argtypes = [NotmuchQueryP] - _count_messages.restype = c_uint - - def __init__(self, db, querystr): - """ - :param db: An open database which we derive the Query from. - :type db: :class:`Database` - :param querystr: The query string for the message. - :type querystr: utf-8 encoded str or unicode - """ - self._db = None - self._query = None - self.sort = None - self.create(db, querystr) - - def _assert_query_is_initialized(self): - """Raises :exc:`NotInitializedError` if self._query is `None`""" - if self._query is None: - raise NotInitializedError() - - def create(self, db, querystr): - """Creates a new query derived from a Database - - This function is utilized by __init__() and usually does not need to - be called directly. - - :param db: Database to create the query from. - :type db: :class:`Database` - :param querystr: The query string - :type querystr: utf-8 encoded str or unicode - :returns: Nothing - :exception: - :exc:`NullPointerError` if the query creation failed - (e.g. too little memory). - :exc:`NotInitializedError` if the underlying db was not - intitialized. - """ - db._assert_db_is_initialized() - # create reference to parent db to keep it alive - self._db = db - # create query, return None if too little mem available - query_p = Query._create(db.db_p, _str(querystr)) - if not query_p: - raise NullPointerError - self._query = query_p - - _set_sort = nmlib.notmuch_query_set_sort - _set_sort.argtypes = [NotmuchQueryP, c_uint] - _set_sort.argtypes = None - - def set_sort(self, sort): - """Set the sort order future results will be delivered in - - :param sort: Sort order (see :attr:`Query.SORT`) - """ - self._assert_query_is_initialized() - self.sort = sort - self._set_sort(self._query, sort) - - def search_threads(self): - """Execute a query for threads - - Execute a query for threads, returning a :class:`Threads` iterator. - The returned threads are owned by the query and as such, will only be - valid until the Query is deleted. - - The method sets :attr:`Message.FLAG`\.MATCH for those messages that - match the query. The method :meth:`Message.get_flag` allows us - to get the value of this flag. - - :returns: :class:`Threads` - :exception: :exc:`NullPointerError` if search_threads failed - """ - self._assert_query_is_initialized() - threads_p = Query._search_threads(self._query) - - if not threads_p: - raise NullPointerError - return Threads(threads_p, self) - - def search_messages(self): - """Filter messages according to the query and return - :class:`Messages` in the defined sort order - - :returns: :class:`Messages` - :exception: :exc:`NullPointerError` if search_messages failed - """ - self._assert_query_is_initialized() - msgs_p = Query._search_messages(self._query) - - if not msgs_p: - raise NullPointerError - return Messages(msgs_p, self) - - def count_messages(self): - """Estimate the number of messages matching the query - - This function performs a search and returns Xapian's best - guess as to the number of matching messages. It is much faster - than performing :meth:`search_messages` and counting the - result with `len()` (although it always returned the same - result in my tests). Technically, it wraps the underlying - *notmuch_query_count_messages* function. - - :returns: :class:`Messages` - """ - self._assert_query_is_initialized() - return Query._count_messages(self._query) - - _destroy = nmlib.notmuch_query_destroy - _destroy.argtypes = [NotmuchQueryP] - _destroy.restype = None - - def __del__(self): - """Close and free the Query""" - if self._query is not None: - self._destroy(self._query) - - class Directory(object): """Represents a directory entry in the notmuch directory diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py new file mode 100644 index 0000000..bab5c3e --- /dev/null +++ b/bindings/python/notmuch/query.py @@ -0,0 +1,192 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth ' +""" + +from ctypes import c_char_p, c_uint +from notmuch.globals import ( + nmlib, + Enum, + _str, + NotmuchQueryP, + NotmuchThreadsP, + NotmuchDatabaseP, + NotmuchMessagesP, + NullPointerError, + NotInitializedError, +) +from notmuch.thread import Threads +from notmuch.message import Messages + + +class Query(object): + """Represents a search query on an opened :class:`Database`. + + A query selects and filters a subset of messages from the notmuch + database we derive from. + + :class:`Query` provides an instance attribute :attr:`sort`, which + contains the sort order (if specified via :meth:`set_sort`) or + `None`. + + Any function in this class may throw an :exc:`NotInitializedError` + in case the underlying query object was not set up correctly. + + .. note:: Do remember that as soon as we tear down this object, + all underlying derived objects such as threads, + messages, tags etc will be freed by the underlying library + as well. Accessing these objects will lead to segfaults and + other unexpected behavior. See above for more details. + """ + # constants + SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED']) + """Constants: Sort order in which to return results""" + + """notmuch_query_create""" + _create = nmlib.notmuch_query_create + _create.argtypes = [NotmuchDatabaseP, c_char_p] + _create.restype = NotmuchQueryP + + """notmuch_query_search_threads""" + _search_threads = nmlib.notmuch_query_search_threads + _search_threads.argtypes = [NotmuchQueryP] + _search_threads.restype = NotmuchThreadsP + + """notmuch_query_search_messages""" + _search_messages = nmlib.notmuch_query_search_messages + _search_messages.argtypes = [NotmuchQueryP] + _search_messages.restype = NotmuchMessagesP + + """notmuch_query_count_messages""" + _count_messages = nmlib.notmuch_query_count_messages + _count_messages.argtypes = [NotmuchQueryP] + _count_messages.restype = c_uint + + def __init__(self, db, querystr): + """ + :param db: An open database which we derive the Query from. + :type db: :class:`Database` + :param querystr: The query string for the message. + :type querystr: utf-8 encoded str or unicode + """ + self._db = None + self._query = None + self.sort = None + self.create(db, querystr) + + def _assert_query_is_initialized(self): + """Raises :exc:`NotInitializedError` if self._query is `None`""" + if self._query is None: + raise NotInitializedError() + + def create(self, db, querystr): + """Creates a new query derived from a Database + + This function is utilized by __init__() and usually does not need to + be called directly. + + :param db: Database to create the query from. + :type db: :class:`Database` + :param querystr: The query string + :type querystr: utf-8 encoded str or unicode + :returns: Nothing + :exception: + :exc:`NullPointerError` if the query creation failed + (e.g. too little memory). + :exc:`NotInitializedError` if the underlying db was not + intitialized. + """ + db._assert_db_is_initialized() + # create reference to parent db to keep it alive + self._db = db + # create query, return None if too little mem available + query_p = Query._create(db.db_p, _str(querystr)) + if not query_p: + raise NullPointerError + self._query = query_p + + _set_sort = nmlib.notmuch_query_set_sort + _set_sort.argtypes = [NotmuchQueryP, c_uint] + _set_sort.argtypes = None + + def set_sort(self, sort): + """Set the sort order future results will be delivered in + + :param sort: Sort order (see :attr:`Query.SORT`) + """ + self._assert_query_is_initialized() + self.sort = sort + self._set_sort(self._query, sort) + + def search_threads(self): + """Execute a query for threads + + Execute a query for threads, returning a :class:`Threads` iterator. + The returned threads are owned by the query and as such, will only be + valid until the Query is deleted. + + The method sets :attr:`Message.FLAG`\.MATCH for those messages that + match the query. The method :meth:`Message.get_flag` allows us + to get the value of this flag. + + :returns: :class:`Threads` + :exception: :exc:`NullPointerError` if search_threads failed + """ + self._assert_query_is_initialized() + threads_p = Query._search_threads(self._query) + + if not threads_p: + raise NullPointerError + return Threads(threads_p, self) + + def search_messages(self): + """Filter messages according to the query and return + :class:`Messages` in the defined sort order + + :returns: :class:`Messages` + :exception: :exc:`NullPointerError` if search_messages failed + """ + self._assert_query_is_initialized() + msgs_p = Query._search_messages(self._query) + + if not msgs_p: + raise NullPointerError + return Messages(msgs_p, self) + + def count_messages(self): + """Estimate the number of messages matching the query + + This function performs a search and returns Xapian's best + guess as to the number of matching messages. It is much faster + than performing :meth:`search_messages` and counting the + result with `len()` (although it always returned the same + result in my tests). Technically, it wraps the underlying + *notmuch_query_count_messages* function. + + :returns: :class:`Messages` + """ + self._assert_query_is_initialized() + return Query._count_messages(self._query) + + _destroy = nmlib.notmuch_query_destroy + _destroy.argtypes = [NotmuchQueryP] + _destroy.restype = None + + def __del__(self): + """Close and free the Query""" + if self._query is not None: + self._destroy(self._query) -- cgit v1.2.3 From 4315ac015a1ba329880031805f4706731b3c1ef4 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 01:13:06 +0100 Subject: python: refactor Query class Put each libnotmuch function reference right in front of the corresponding python wrapper. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/query.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index bab5c3e..d4a93d5 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -56,26 +56,6 @@ class Query(object): SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED']) """Constants: Sort order in which to return results""" - """notmuch_query_create""" - _create = nmlib.notmuch_query_create - _create.argtypes = [NotmuchDatabaseP, c_char_p] - _create.restype = NotmuchQueryP - - """notmuch_query_search_threads""" - _search_threads = nmlib.notmuch_query_search_threads - _search_threads.argtypes = [NotmuchQueryP] - _search_threads.restype = NotmuchThreadsP - - """notmuch_query_search_messages""" - _search_messages = nmlib.notmuch_query_search_messages - _search_messages.argtypes = [NotmuchQueryP] - _search_messages.restype = NotmuchMessagesP - - """notmuch_query_count_messages""" - _count_messages = nmlib.notmuch_query_count_messages - _count_messages.argtypes = [NotmuchQueryP] - _count_messages.restype = c_uint - def __init__(self, db, querystr): """ :param db: An open database which we derive the Query from. @@ -93,6 +73,11 @@ class Query(object): if self._query is None: raise NotInitializedError() + """notmuch_query_create""" + _create = nmlib.notmuch_query_create + _create.argtypes = [NotmuchDatabaseP, c_char_p] + _create.restype = NotmuchQueryP + def create(self, db, querystr): """Creates a new query derived from a Database @@ -132,6 +117,11 @@ class Query(object): self.sort = sort self._set_sort(self._query, sort) + """notmuch_query_search_threads""" + _search_threads = nmlib.notmuch_query_search_threads + _search_threads.argtypes = [NotmuchQueryP] + _search_threads.restype = NotmuchThreadsP + def search_threads(self): """Execute a query for threads @@ -153,6 +143,11 @@ class Query(object): raise NullPointerError return Threads(threads_p, self) + """notmuch_query_search_messages""" + _search_messages = nmlib.notmuch_query_search_messages + _search_messages.argtypes = [NotmuchQueryP] + _search_messages.restype = NotmuchMessagesP + def search_messages(self): """Filter messages according to the query and return :class:`Messages` in the defined sort order @@ -167,6 +162,11 @@ class Query(object): raise NullPointerError return Messages(msgs_p, self) + """notmuch_query_count_messages""" + _count_messages = nmlib.notmuch_query_count_messages + _count_messages.argtypes = [NotmuchQueryP] + _count_messages.restype = c_uint + def count_messages(self): """Estimate the number of messages matching the query -- cgit v1.2.3 From ff8f864245c3ac54cc23aa47af09c95cde484abb Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 01:32:21 +0100 Subject: python: wrap notmuch_query_count_threads as Query.count_threads Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 2 ++ bindings/python/notmuch/query.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index f7d3d60..5405de8 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -128,6 +128,8 @@ More information on specific topics can be found on the following pages: .. automethod:: count_messages + .. automethod:: count_threads + :class:`Messages` -- A bunch of messages ---------------------------------------- diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index d4a93d5..14c00cd 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -182,6 +182,25 @@ class Query(object): self._assert_query_is_initialized() return Query._count_messages(self._query) + _count_threads = nmlib.notmuch_query_count_threads + _count_threads.argtypes = [NotmuchQueryP] + _count_threads.restype = c_uint + + def count_threads(self): + ''' + This function performs a search and returns the number of + unique thread IDs in the matching messages. This is the same + as number of threads matching a search. + + Note that this is a significantly heavier operation than + meth:`Query.count_messages`. + + :returns: the number of threads returned by this query + :rtype: int + ''' + self._assert_query_is_initialized() + return Query._count_threads(self._query) + _destroy = nmlib.notmuch_query_destroy _destroy.argtypes = [NotmuchQueryP] _destroy.restype = None -- cgit v1.2.3 From bf6039e34eca52ccf7fe1db51e1b5c843a9828f3 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 01:38:19 +0100 Subject: python: improve Query.count_messages docstring Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/query.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index 14c00cd..0c08aa9 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -162,23 +162,18 @@ class Query(object): raise NullPointerError return Messages(msgs_p, self) - """notmuch_query_count_messages""" _count_messages = nmlib.notmuch_query_count_messages _count_messages.argtypes = [NotmuchQueryP] _count_messages.restype = c_uint def count_messages(self): - """Estimate the number of messages matching the query - + ''' This function performs a search and returns Xapian's best - guess as to the number of matching messages. It is much faster - than performing :meth:`search_messages` and counting the - result with `len()` (although it always returned the same - result in my tests). Technically, it wraps the underlying - *notmuch_query_count_messages* function. + guess as to the number of matching messages. - :returns: :class:`Messages` - """ + :returns: the estimated number of messages matching this query + :rtype: int + ''' self._assert_query_is_initialized() return Query._count_messages(self._query) -- cgit v1.2.3 From ab2f9fd828058d281480e9947ea346e382a7f3c8 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 18 Feb 2012 01:41:23 +0100 Subject: python: remove trailing whitespace within the documentation source files Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 14 +++++++------- bindings/python/docs/source/notmuch.rst | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index 5405de8..25eb146 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -13,7 +13,7 @@ Within :mod:`notmuch`, the classes :class:`Database`, :class:`Query` provide mos :License: This module is covered under the GNU GPL v3 (or later). -This page contains the main API overview of notmuch |release|. +This page contains the main API overview of notmuch |release|. Notmuch can be imported as:: @@ -35,7 +35,7 @@ More information on specific topics can be found on the following pages: :maxdepth: 1 status_and_errors - notmuch + notmuch :mod:`notmuch` -- The Notmuch interface ================================================= @@ -111,8 +111,8 @@ More information on specific topics can be found on the following pages: Sort by email message ID. SORT.UNSORTED - Do not apply a special sort order (returns results in document id - order). + Do not apply a special sort order (returns results in document id + order). .. automethod:: set_sort @@ -141,7 +141,7 @@ More information on specific topics can be found on the following pages: .. method:: __len__() .. warning:: - + :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`. @@ -162,7 +162,7 @@ More information on specific topics can be found on the following pages: .. attribute:: FLAG - FLAG.MATCH + FLAG.MATCH This flag is automatically set by a Query.search_threads on those messages that match the query. This allows us to distinguish matches from the rest @@ -171,7 +171,7 @@ More information on specific topics can be found on the following pages: .. automethod:: get_flag .. automethod:: set_flag - + .. automethod:: get_date .. automethod:: get_header diff --git a/bindings/python/docs/source/notmuch.rst b/bindings/python/docs/source/notmuch.rst index 32e1783..bf68f33 100644 --- a/bindings/python/docs/source/notmuch.rst +++ b/bindings/python/docs/source/notmuch.rst @@ -29,7 +29,7 @@ Where and [args...] are as follows: **show** [...] Show all messages matching the search terms. - This has been partially implemented, we show a stub for each + This has been partially implemented, we show a stub for each found message, but do not output the full message body yet. **count** [...] -- cgit v1.2.3 From be851ad39de11f38e1cd4f7f15f1fa952232efe2 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sun, 19 Feb 2012 00:36:15 +0100 Subject: python: more error handling fixes This is a follow up commit to 221c7e0b38177f5f1dbf0561580c15e8aaa49004 fixing more NULL pointer checks. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 2 +- bindings/python/notmuch/filename.py | 2 +- bindings/python/notmuch/message.py | 40 ++++++++++++++++++------------------- bindings/python/notmuch/query.py | 2 +- bindings/python/notmuch/tag.py | 2 +- bindings/python/notmuch/thread.py | 26 ++++++++++++------------ 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 0958ce0..6edb18b 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -159,7 +159,7 @@ class Database(object): def _assert_db_is_initialized(self): """Raises :exc:`NotInitializedError` if self._db is `None`""" - if self._db is None: + if not self._db: raise NotInitializedError() def create(self, path): diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index 469b6a5..322e6bf 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -89,7 +89,7 @@ class Filenames(Python3StringMixIn): This is the main function that will usually be used by the user.""" - if self._files is None: + if not self._files: raise NotmuchError(STATUS.NOT_INITIALIZED) while self._valid(self._files): diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 883ed23..28723c1 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -135,7 +135,7 @@ class Messages(object): :meth:`collect_tags` will iterate over the messages and therefore will not allow further iterations. """ - if self._msgs is None: + if not self._msgs: raise NotmuchError(STATUS.NOT_INITIALIZED) # collect all tags (returns NULL on error) @@ -160,7 +160,7 @@ class Messages(object): _move_to_next.restype = None def __next__(self): - if self._msgs is None: + if not self._msgs: raise NotmuchError(STATUS.NOT_INITIALIZED) if not self._valid(self._msgs): @@ -362,7 +362,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._get_message_id(self._msg).decode('utf-8', 'ignore') @@ -379,7 +379,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._get_thread_id(self._msg).decode('utf-8', 'ignore') @@ -402,7 +402,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) msgs_p = Message._get_replies(self._msg) @@ -424,7 +424,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._get_date(self._msg) @@ -447,7 +447,7 @@ class Message(Python3StringMixIn): is not initialized. * STATUS.NULL_POINTER if any error occured. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) #Returns NULL if any error occurs. @@ -463,7 +463,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._get_filename(self._msg).decode('utf-8', 'ignore') @@ -473,7 +473,7 @@ class Message(Python3StringMixIn): Returns a Filenames() generator with all absolute filepaths for messages recorded to have the same Message-ID. These files must not necessarily have identical content.""" - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) files_p = Message._get_filenames(self._msg) @@ -493,7 +493,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._get_flag(self._msg, flag) @@ -508,7 +508,7 @@ class Message(Python3StringMixIn): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) self._set_flag(self._msg, flag, value) @@ -522,7 +522,7 @@ class Message(Python3StringMixIn): is not initialized. * STATUS.NULL_POINTER, on error """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) tags_p = Message._get_tags(self._msg) @@ -565,7 +565,7 @@ class Message(Python3StringMixIn): STATUS.NOT_INITIALIZED The message has not been initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) status = self._add_tag(self._msg, _str(tag)) @@ -613,7 +613,7 @@ class Message(Python3StringMixIn): STATUS.NOT_INITIALIZED The message has not been initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) status = self._remove_tag(self._msg, _str(tag)) @@ -654,7 +654,7 @@ class Message(Python3StringMixIn): STATUS.NOT_INITIALIZED The message has not been initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) status = self._remove_all_tags(self._msg) @@ -712,7 +712,7 @@ class Message(Python3StringMixIn): STATUS.NOT_INITIALIZED The message has not been initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) status = self._freeze(self._msg) @@ -751,7 +751,7 @@ class Message(Python3StringMixIn): STATUS.NOT_INITIALIZED The message has not been initialized. """ - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) status = self._thaw(self._msg) @@ -787,7 +787,7 @@ class Message(Python3StringMixIn): :returns: a :class:`STATUS` value. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._tags_to_maildir_flags(self._msg) @@ -814,7 +814,7 @@ class Message(Python3StringMixIn): :returns: a :class:`STATUS`. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" - if self._msg is None: + if not self._msg: raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._tags_to_maildir_flags(self._msg) @@ -957,7 +957,7 @@ class Message(Python3StringMixIn): def __hash__(self): """Implement hash(), so we can use Message() sets""" file = self.get_filename() - if file is None: + if not file: return None return hash(file) diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index 0c08aa9..6132ca0 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -70,7 +70,7 @@ class Query(object): def _assert_query_is_initialized(self): """Raises :exc:`NotInitializedError` if self._query is `None`""" - if self._query is None: + if not self._query: raise NotInitializedError() """notmuch_query_create""" diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index d2dc498..d0f7bb4 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -90,7 +90,7 @@ class Tags(Python3StringMixIn): _move_to_next.restype = None def __next__(self): - if self._tags is None: + if not self._tags: raise NotmuchError(STATUS.NOT_INITIALIZED) if not self._valid(self._tags): self._tags = None diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index 104710c..c2347fe 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -117,7 +117,7 @@ class Threads(Python3StringMixIn): _move_to_next.restype = None def __next__(self): - if self._threads is None: + if not self._threads: raise NotmuchError(STATUS.NOT_INITIALIZED) if not self._valid(self._threads): @@ -141,7 +141,7 @@ class Threads(Python3StringMixIn): # next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!! for thread in threads: print thread """ - if self._threads is None: + if not self._threads: raise NotmuchError(STATUS.NOT_INITIALIZED) i = 0 @@ -244,7 +244,7 @@ class Thread(object): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread is not initialized. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore') @@ -261,7 +261,7 @@ class Thread(object): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread is not initialized. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) return self._get_total_messages(self._thread) @@ -284,7 +284,7 @@ class Thread(object): * STATUS.NOT_INITIALIZED if query is not inited * STATUS.NULL_POINTER if search_messages failed """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) msgs_p = Thread._get_toplevel_messages(self._thread) @@ -307,7 +307,7 @@ class Thread(object): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread is not initialized. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) return self._get_matched_messages(self._thread) @@ -321,10 +321,10 @@ class Thread(object): The returned string belongs to 'thread' and will only be valid for as long as this Thread() is not deleted. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) authors = Thread._get_authors(self._thread) - if authors is None: + if not authors: return None return authors.decode('UTF-8', 'ignore') @@ -334,10 +334,10 @@ class Thread(object): The returned string belongs to 'thread' and will only be valid for as long as this Thread() is not deleted. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) subject = Thread._get_subject(self._thread) - if subject is None: + if not subject: return None return subject.decode('UTF-8', 'ignore') @@ -349,7 +349,7 @@ class Thread(object): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) return Thread._get_newest_date(self._thread) @@ -361,7 +361,7 @@ class Thread(object): :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) return Thread._get_oldest_date(self._thread) @@ -384,7 +384,7 @@ class Thread(object): is not initialized. * STATUS.NULL_POINTER, on error """ - if self._thread is None: + if not self._thread: raise NotmuchError(STATUS.NOT_INITIALIZED) tags_p = Thread._get_tags(self._thread) -- cgit v1.2.3 From 4bb9f59ff6da456392ffaf9871941203e4cf9b53 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 20 Feb 2012 21:48:35 +0100 Subject: python: fix the projects name and update years of the copyright notice in the sphinx docs Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py index 76610b7..9db377f 100644 --- a/bindings/python/docs/source/conf.py +++ b/bindings/python/docs/source/conf.py @@ -57,8 +57,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'cnotmuch' -copyright = u'2010, ' + __AUTHOR__ +project = u'notmuch' +copyright = u'2010-2012, ' + __AUTHOR__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the -- cgit v1.2.3 From a1442952d4d7fad8b7612502802ee346ac8fd349 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 20 Feb 2012 23:49:07 +0100 Subject: python: refactor the error handling machinery Raise specific error classes instead of a generic NotmuchError with an magic status value (e.g. NotmuchError(STATUS.NULL_POINTER) -> NullPointerError()), update the documentation accordingly. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 15 +-- bindings/python/notmuch/filename.py | 26 ++++-- bindings/python/notmuch/message.py | 181 +++++++++++++++++------------------- bindings/python/notmuch/tag.py | 26 ++++-- bindings/python/notmuch/thread.py | 78 ++++++++-------- 5 files changed, 165 insertions(+), 161 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 6edb18b..3de0f2b 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -23,7 +23,9 @@ from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER from notmuch.globals import ( nmlib, STATUS, + FileError, NotmuchError, + NullPointerError, NotInitializedError, Enum, _str, @@ -355,9 +357,8 @@ class Database(object): # we got an absolute path if not path.startswith(self.get_path()): # but its initial components are not equal to the db path - raise NotmuchError(STATUS.FILE_ERROR, - message="Database().get_directory() called " - "with a wrong absolute path.") + raise FileError('Database().get_directory() called ' + 'with a wrong absolute path') abs_dirpath = path else: #we got a relative path, make it absolute @@ -542,7 +543,7 @@ class Database(object): self._assert_db_is_initialized() tags_p = Database._get_all_tags(self._db) if tags_p == None: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() return Tags(tags_p, self) def create_query(self, querystring): @@ -636,7 +637,7 @@ class Directory(object): """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None""" if not self._dir_p: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() def __init__(self, path, dir_p, parent): """ @@ -797,7 +798,7 @@ class Filenames(object): def __next__(self): if not self._files_p: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() if not self._valid(self._files_p): self._files_p = None @@ -824,7 +825,7 @@ class Filenames(object): for file in files: print file """ if not self._files_p: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() i = 0 while self._valid(self._files_p): diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index 322e6bf..353eb76 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -17,8 +17,14 @@ along with notmuch. If not, see . Copyright 2010 Sebastian Spaeth ' """ from ctypes import c_char_p -from notmuch.globals import (nmlib, STATUS, NotmuchError, - NotmuchFilenamesP, NotmuchMessageP, Python3StringMixIn) +from notmuch.globals import ( + nmlib, + NullPointerError, + NotInitializedError, + NotmuchMessageP, + NotmuchFilenamesP, + Python3StringMixIn, +) class Filenames(Python3StringMixIn): @@ -29,9 +35,9 @@ class Filenames(Python3StringMixIn): iterator over a list of notmuch filenames. Do note that the underlying library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of - tags, and a subsequent iteration attempt will raise a :exc:`NotmuchError` - STATUS.NOT_INITIALIZED. Also note, that any function that uses iteration - (nearly all) will also exhaust the tags. So both:: + tags, and a subsequent iteration attempt will raise a + :exc:`NotInitializedError`. Also note, that any function that uses + iteration (nearly all) will also exhaust the tags. So both:: for name in filenames: print name @@ -61,8 +67,8 @@ class Filenames(Python3StringMixIn): will almost never instantiate a :class:`Tags` object herself. They are usually handed back as a result, e.g. in :meth:`Database.get_all_tags`. *tags_p* must be - valid, we will raise an :exc:`NotmuchError` - (STATUS.NULL_POINTER) if it is `None`. + valid, we will raise an :exc:`NullPointerError` + if it is `None`. :type files_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Message` these filenames are derived from, and saves a @@ -70,7 +76,7 @@ class Filenames(Python3StringMixIn): once all derived objects are dead. """ if not files_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() self._files = files_p #save reference to parent object so we keep it alive @@ -90,7 +96,7 @@ class Filenames(Python3StringMixIn): This is the main function that will usually be used by the user.""" if not self._files: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() while self._valid(self._files): yield Filenames._get(self._files).decode('utf-8', 'ignore') @@ -104,7 +110,7 @@ class Filenames(Python3StringMixIn): .. note:: As this iterates over the filenames, we will not be able to iterate over them again (as in retrieve them)! If the tags have been exhausted already, this will raise a - :exc:`NotmuchError` STATUS.NOT_INITIALIZED on subsequent + :exc:`NotInitializedError` on subsequent attempts. However, you can use :meth:`Message.get_filenames` repeatedly to perform various actions on filenames. diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 28723c1..b291f9f 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -22,8 +22,19 @@ Copyright 2010 Sebastian Spaeth ' from ctypes import c_char_p, c_long, c_uint, c_int from datetime import date from notmuch.globals import ( - nmlib, STATUS, NotmuchError, Enum, _str, Python3StringMixIn, - NotmuchTagsP, NotmuchMessagesP, NotmuchMessageP, NotmuchFilenamesP) + nmlib, + Enum, + _str, + Python3StringMixIn, + STATUS, + NotmuchError, + NullPointerError, + NotInitializedError, + NotmuchTagsP, + NotmuchMessageP, + NotmuchMessagesP, + NotmuchFilenamesP, +) from notmuch.tag import Tags from notmuch.filename import Filenames import sys @@ -43,7 +54,7 @@ class Messages(object): only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of messages, and a subsequent iteration attempt will raise a - :exc:`NotmuchError` STATUS.NOT_INITIALIZED. If you need to + :exc:`NotInitializedError`. If you need to re-iterate over a list of messages you will need to retrieve a new :class:`Messages` object or cache your :class:`Message`\s in a list via:: @@ -107,8 +118,8 @@ class Messages(object): will almost never instantiate a :class:`Messages` object herself. They are usually handed back as a result, e.g. in :meth:`Query.search_messages`. *msgs_p* must be - valid, we will raise an :exc:`NotmuchError` - (STATUS.NULL_POINTER) if it is `None`. + valid, we will raise an :exc:`NullPointerError` if it is + `None`. :type msgs_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Query`) these tags are derived from. It saves @@ -118,7 +129,7 @@ class Messages(object): the Python object.(?) """ if not msgs_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() self._msgs = msgs_p #store parent, so we keep them alive as long as self is alive @@ -128,7 +139,7 @@ class Messages(object): """Return the unique :class:`Tags` in the contained messages :returns: :class:`Tags` - :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not init'ed + :exceptions: :exc:`NotInitializedError` if not init'ed .. note:: @@ -136,7 +147,7 @@ class Messages(object): will not allow further iterations. """ if not self._msgs: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() # collect all tags (returns NULL on error) tags_p = Messages._collect_tags(self._msgs) @@ -144,7 +155,7 @@ class Messages(object): self._msgs = None if tags_p == None: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() return Tags(tags_p, self) def __iter__(self): @@ -161,7 +172,7 @@ class Messages(object): def __next__(self): if not self._msgs: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() if not self._valid(self._msgs): self._msgs = None @@ -341,8 +352,8 @@ class Message(Python3StringMixIn): def __init__(self, msg_p, parent=None): """ :param msg_p: A pointer to an internal notmuch_message_t - Structure. If it is `None`, we will raise an :exc:`NotmuchError` - STATUS.NULL_POINTER. + Structure. If it is `None`, we will raise an + :exc:`NullPointerError`. :param parent: A 'parent' object is passed which this message is derived from. We save a reference to it, so we can @@ -350,7 +361,7 @@ class Message(Python3StringMixIn): objects are dead. """ if not msg_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() self._msg = msg_p #keep reference to parent, so we keep it alive self._parent = parent @@ -359,11 +370,11 @@ class Message(Python3StringMixIn): """Returns the message ID :returns: String with a message ID - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._get_message_id(self._msg).decode('utf-8', 'ignore') def get_thread_id(self): @@ -376,11 +387,11 @@ class Message(Python3StringMixIn): message belongs to a single thread. :returns: String with a thread ID - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._get_thread_id(self._msg).decode('utf-8', 'ignore') @@ -399,11 +410,11 @@ class Message(Python3StringMixIn): an empty Messages iterator. :returns: :class:`Messages`. - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() msgs_p = Message._get_replies(self._msg) @@ -421,11 +432,11 @@ class Message(Python3StringMixIn): :returns: A time_t timestamp. :rtype: c_unit64 - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._get_date(self._msg) def get_header(self, header): @@ -441,30 +452,28 @@ class Message(Python3StringMixIn): It is not case-sensitive. :type header: str :returns: The header value as string - :exception: :exc:`NotmuchError` - - * STATUS.NOT_INITIALIZED if the message - is not initialized. - * STATUS.NULL_POINTER if any error occured. + :raises: :exc:`NotInitializedError` if the message is not + initialized + :raises: :exc:`NullPointerError` if any error occured """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() #Returns NULL if any error occurs. header = Message._get_header(self._msg, _str(header)) if header == None: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() return header.decode('UTF-8', 'ignore') def get_filename(self): """Returns the file path of the message file :returns: Absolute file path & name of the message file - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._get_filename(self._msg).decode('utf-8', 'ignore') def get_filenames(self): @@ -474,7 +483,7 @@ class Message(Python3StringMixIn): messages recorded to have the same Message-ID. These files must not necessarily have identical content.""" if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() files_p = Message._get_filenames(self._msg) @@ -490,11 +499,11 @@ class Message(Python3StringMixIn): :param flag: One of the :attr:`Message.FLAG` values (currently only *Message.FLAG.MATCH* :returns: An unsigned int (0/1), indicating whether the flag is set. - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._get_flag(self._msg, flag) def set_flag(self, flag, value): @@ -505,29 +514,27 @@ class Message(Python3StringMixIn): :param value: A bool indicating whether to set or unset the flag. :returns: Nothing - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() self._set_flag(self._msg, flag, value) def get_tags(self): """Returns the message tags :returns: A :class:`Tags` iterator. - :exception: :exc:`NotmuchError` - - * STATUS.NOT_INITIALIZED if the message - is not initialized. - * STATUS.NULL_POINTER, on error + :raises: :exc:`NotInitializedError` if the message is not + initialized + :raises: :exc:`NullPointerError` if any error occured """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() tags_p = Message._get_tags(self._msg) if tags_p == None: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() return Tags(tags_p, self) _add_tag = nmlib.notmuch_message_add_tag @@ -552,21 +559,16 @@ class Message(Python3StringMixIn): :returns: STATUS.SUCCESS if the tag was successfully added. Raises an exception otherwise. - :exception: :exc:`NotmuchError`. They have the following meaning: - - STATUS.NULL_POINTER - The 'tag' argument is NULL - STATUS.TAG_TOO_LONG - The length of 'tag' is too long - (exceeds Message.NOTMUCH_TAG_MAX) - STATUS.READ_ONLY_DATABASE - Database was opened in read-only mode so message cannot be - modified. - STATUS.NOT_INITIALIZED - The message has not been initialized. - """ + :raises: :exc:`NullPointerError` if the `tag` argument is NULL + :raises: :exc:`TagTooLongError` if the length of `tag` exceeds + Message.NOTMUCH_TAG_MAX) + :raises: :exc:`ReadOnlyDatabaseError` if the database was opened + in read-only mode so message cannot be modified + :raises: :exc:`NotInitializedError` if message has not been + initialized + """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() status = self._add_tag(self._msg, _str(tag)) @@ -600,21 +602,16 @@ class Message(Python3StringMixIn): :returns: STATUS.SUCCESS if the tag was successfully removed or if the message had no such tag. Raises an exception otherwise. - :exception: :exc:`NotmuchError`. They have the following meaning: - - STATUS.NULL_POINTER - The 'tag' argument is NULL - STATUS.TAG_TOO_LONG - The length of 'tag' is too long - (exceeds NOTMUCH_TAG_MAX) - STATUS.READ_ONLY_DATABASE - Database was opened in read-only mode so message cannot - be modified. - STATUS.NOT_INITIALIZED - The message has not been initialized. + :raises: :exc:`NullPointerError` if the `tag` argument is NULL + :raises: :exc:`TagTooLongError` if the length of `tag` exceeds + Message.NOTMUCH_TAG_MAX) + :raises: :exc:`ReadOnlyDatabaseError` if the database was opened + in read-only mode so message cannot be modified + :raises: :exc:`NotInitializedError` if message has not been + initialized """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() status = self._remove_tag(self._msg, _str(tag)) # bail out on error @@ -646,16 +643,13 @@ class Message(Python3StringMixIn): :returns: STATUS.SUCCESS if the tags were successfully removed. Raises an exception otherwise. - :exception: :exc:`NotmuchError`. They have the following meaning: - - STATUS.READ_ONLY_DATABASE - Database was opened in read-only mode so message cannot - be modified. - STATUS.NOT_INITIALIZED - The message has not been initialized. + :raises: :exc:`ReadOnlyDatabaseError` if the database was opened + in read-only mode so message cannot be modified + :raises: :exc:`NotInitializedError` if message has not been + initialized """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() status = self._remove_all_tags(self._msg) @@ -704,16 +698,13 @@ class Message(Python3StringMixIn): :returns: STATUS.SUCCESS if the message was successfully frozen. Raises an exception otherwise. - :exception: :exc:`NotmuchError`. They have the following meaning: - - STATUS.READ_ONLY_DATABASE - Database was opened in read-only mode so message cannot - be modified. - STATUS.NOT_INITIALIZED - The message has not been initialized. + :raises: :exc:`ReadOnlyDatabaseError` if the database was opened + in read-only mode so message cannot be modified + :raises: :exc:`NotInitializedError` if message has not been + initialized """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() status = self._freeze(self._msg) @@ -742,17 +733,15 @@ class Message(Python3StringMixIn): :returns: STATUS.SUCCESS if the message was successfully frozen. Raises an exception otherwise. - :exception: :exc:`NotmuchError`. They have the following meaning: - - STATUS.UNBALANCED_FREEZE_THAW - An attempt was made to thaw an unfrozen message. - That is, there have been an unbalanced number of calls - to :meth:`freeze` and :meth:`thaw`. - STATUS.NOT_INITIALIZED - The message has not been initialized. + :raises: :exc:`UnbalancedFreezeThawError` if an attempt was made + to thaw an unfrozen message. That is, there have been + an unbalanced number of calls to :meth:`freeze` and + :meth:`thaw`. + :raises: :exc:`NotInitializedError` if message has not been + initialized """ if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() status = self._thaw(self._msg) @@ -788,7 +777,7 @@ class Message(Python3StringMixIn): :returns: a :class:`STATUS` value. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._tags_to_maildir_flags(self._msg) def maildir_flags_to_tags(self): @@ -815,7 +804,7 @@ class Message(Python3StringMixIn): :returns: a :class:`STATUS`. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" if not self._msg: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Message._tags_to_maildir_flags(self._msg) def __repr__(self): diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index d0f7bb4..526e51c 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -17,7 +17,13 @@ along with notmuch. If not, see . Copyright 2010 Sebastian Spaeth ' """ from ctypes import c_char_p -from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP, Python3StringMixIn +from notmuch.globals import ( + nmlib, + Python3StringMixIn, + NullPointerError, + NotInitializedError, + NotmuchTagsP, +) class Tags(Python3StringMixIn): @@ -29,9 +35,9 @@ class Tags(Python3StringMixIn): Do note that the underlying library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of tags, and a subsequent - iteration attempt will raise a :exc:`NotmuchError` - STATUS.NOT_INITIALIZED. Also note, that any function that uses - iteration (nearly all) will also exhaust the tags. So both:: + iteration attempt will raise a :exc:`NotInitializedError`. + Also note, that any function that uses iteration (nearly all) will + also exhaust the tags. So both:: for tag in tags: print tag @@ -60,8 +66,8 @@ class Tags(Python3StringMixIn): will almost never instantiate a :class:`Tags` object herself. They are usually handed back as a result, e.g. in :meth:`Database.get_all_tags`. *tags_p* must be - valid, we will raise an :exc:`NotmuchError` - (STATUS.NULL_POINTER) if it is `None`. + valid, we will raise an :exc:`NullPointerError` if it is + `None`. :type tags_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Database` or :class:`Message` these tags are derived from, and saves a @@ -71,7 +77,7 @@ class Tags(Python3StringMixIn): cache the tags in the Python object(?) """ if not tags_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() self._tags = tags_p #save reference to parent object so we keep it alive @@ -91,7 +97,7 @@ class Tags(Python3StringMixIn): def __next__(self): if not self._tags: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() if not self._valid(self._tags): self._tags = None raise StopIteration @@ -118,8 +124,8 @@ class Tags(Python3StringMixIn): As this iterates over the tags, we will not be able to iterate over them again (as in retrieve them)! If the tags have been exhausted - already, this will raise a :exc:`NotmuchError` - STATUS.NOT_INITIALIZED on subsequent attempts. + already, this will raise a :exc:`NotInitializedError`on subsequent + attempts. """ return " ".join(self) diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index c2347fe..5c58028 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -18,9 +18,16 @@ Copyright 2010 Sebastian Spaeth ' """ from ctypes import c_char_p, c_long, c_int -from notmuch.globals import (nmlib, STATUS, - NotmuchError, NotmuchThreadP, NotmuchThreadsP, NotmuchMessagesP, - NotmuchTagsP, Python3StringMixIn) +from notmuch.globals import ( + nmlib, + Python3StringMixIn, + NullPointerError, + NotInitializedError, + NotmuchThreadP, + NotmuchThreadsP, + NotmuchMessagesP, + NotmuchTagsP, +) from notmuch.message import Messages from notmuch.tag import Tags from datetime import date @@ -35,7 +42,7 @@ class Threads(Python3StringMixIn): library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of threads, and a subsequent iteration attempt - will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also + will raise a :exc:`NotInitializedError`. Also note, that any function that uses iteration will also exhaust the messages. So both:: @@ -87,8 +94,8 @@ class Threads(Python3StringMixIn): will almost never instantiate a :class:`Threads` object herself. They are usually handed back as a result, e.g. in :meth:`Query.search_threads`. *threads_p* must be - valid, we will raise an :exc:`NotmuchError` - (STATUS.NULL_POINTER) if it is `None`. + valid, we will raise an :exc:`NullPointerError` if it is + `None`. :type threads_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Query`) these tags are derived from. It saves @@ -98,7 +105,7 @@ class Threads(Python3StringMixIn): the Python object.(?) """ if not threads_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() self._threads = threads_p #store parent, so we keep them alive as long as self is alive @@ -118,7 +125,7 @@ class Threads(Python3StringMixIn): def __next__(self): if not self._threads: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() if not self._valid(self._threads): self._threads = None @@ -138,11 +145,11 @@ class Threads(Python3StringMixIn): #THIS FAILS threads = Database().create_query('').search_threads() if len(threads) > 0: #this 'exhausts' threads - # next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!! + # next line raises :exc:`NotInitializedError`!!! for thread in threads: print thread """ if not self._threads: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() i = 0 # returns 'bool'. On out-of-memory it returns None @@ -220,8 +227,8 @@ class Thread(object): will almost never instantiate a :class:`Thread` object herself. They are usually handed back as a result, e.g. when iterating through :class:`Threads`. *thread_p* - must be valid, we will raise an :exc:`NotmuchError` - (STATUS.NULL_POINTER) if it is `None`. + must be valid, we will raise an :exc:`NullPointerError` + if it is `None`. :param parent: A 'parent' object is passed which this message is derived from. We save a reference to it, so we can @@ -229,7 +236,7 @@ class Thread(object): objects are dead. """ if not thread_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() self._thread = thread_p #keep reference to parent, so we keep it alive self._parent = parent @@ -241,11 +248,11 @@ class Thread(object): for as long as the thread is valid. :returns: String with a message ID - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread + :exception: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore') _get_total_messages = nmlib.notmuch_thread_get_total_messages @@ -258,11 +265,11 @@ class Thread(object): :returns: The number of all messages in the database belonging to this thread. Contrast with :meth:`get_matched_messages`. - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread + :exception: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return self._get_total_messages(self._thread) def get_toplevel_messages(self): @@ -279,18 +286,16 @@ class Thread(object): messages, etc.). :returns: :class:`Messages` - :exception: :exc:`NotmuchError` - - * STATUS.NOT_INITIALIZED if query is not inited - * STATUS.NULL_POINTER if search_messages failed + :raises: :exc:`NotInitializedError` if query is not initialized + :raises: :exc:`NullPointerError` if search_messages failed """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() msgs_p = Thread._get_toplevel_messages(self._thread) if not msgs_p: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() return Messages(msgs_p, self) @@ -304,11 +309,11 @@ class Thread(object): :returns: The number of all messages belonging to this thread that matched the :class:`Query`from which this thread was created. Contrast with :meth:`get_total_messages`. - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread + :exception: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return self._get_matched_messages(self._thread) def get_authors(self): @@ -322,7 +327,7 @@ class Thread(object): as long as this Thread() is not deleted. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() authors = Thread._get_authors(self._thread) if not authors: return None @@ -335,7 +340,7 @@ class Thread(object): as long as this Thread() is not deleted. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() subject = Thread._get_subject(self._thread) if not subject: return None @@ -346,11 +351,11 @@ class Thread(object): :returns: A time_t timestamp. :rtype: c_unit64 - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Thread._get_newest_date(self._thread) def get_oldest_date(self): @@ -358,11 +363,11 @@ class Thread(object): :returns: A time_t timestamp. :rtype: c_unit64 - :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message + :exception: :exc:`NotInitializedError` if the message is not initialized. """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() return Thread._get_oldest_date(self._thread) def get_tags(self): @@ -378,18 +383,15 @@ class Thread(object): query from which it derived is explicitely deleted). :returns: A :class:`Tags` iterator. - :exception: :exc:`NotmuchError` - - * STATUS.NOT_INITIALIZED if the thread - is not initialized. - * STATUS.NULL_POINTER, on error + :raises: :exc:`NotInitializedError` if query is not initialized + :raises: :exc:`NullPointerError` if search_messages failed """ if not self._thread: - raise NotmuchError(STATUS.NOT_INITIALIZED) + raise NotInitializedError() tags_p = Thread._get_tags(self._thread) if tags_p == None: - raise NotmuchError(STATUS.NULL_POINTER) + raise NullPointerError() return Tags(tags_p, self) def __unicode__(self): -- cgit v1.2.3 From 798b74e859734d12c953390bca0753f8e5e1d67c Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Tue, 21 Feb 2012 00:01:23 +0100 Subject: python: harmonize the sphinx keyword for exceptions Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 24 ++++++++++++------------ bindings/python/notmuch/message.py | 14 +++++++------- bindings/python/notmuch/query.py | 6 +++--- bindings/python/notmuch/thread.py | 10 +++++----- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 3de0f2b..42a4442 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -140,7 +140,7 @@ class Database(object): :param mode: Mode to open a database in. Is always :attr:`MODE`.READ_WRITE when creating a new one. :type mode: :attr:`MODE` - :exception: :exc:`NotmuchError` or derived exception in case of + :raises: :exc:`NotmuchError` or derived exception in case of failure. """ self._db = None @@ -177,7 +177,7 @@ class Database(object): :param path: A directory in which we should create the database. :type path: str :returns: Nothing - :exception: :exc:`NotmuchError` in case of any failure + :raises: :exc:`NotmuchError` in case of any failure (possibly after printing an error message on stderr). """ if self._db is not None: @@ -201,7 +201,7 @@ class Database(object): :param status: Open the database in read-only or read-write mode :type status: :attr:`MODE` :returns: Nothing - :exception: Raises :exc:`NotmuchError` in case of any failure + :raises: Raises :exc:`NotmuchError` in case of any failure (possibly after printing an error message on stderr). """ res = Database._open(_str(path), mode) @@ -296,7 +296,7 @@ class Database(object): neither begin nor end necessarily flush modifications to disk. :returns: :attr:`STATUS`.SUCCESS or raises - :exception: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION + :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION Xapian exception occurred; atomic section not entered. *Added in notmuch 0.9*""" @@ -317,7 +317,7 @@ class Database(object): :returns: :attr:`STATUS`.SUCCESS or raises - :exception: + :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION A Xapian exception occurred; atomic section not @@ -346,7 +346,7 @@ class Database(object): of database (see :meth:`get_path`), or else should be an absolute path with initial components that match the path of 'database'. :returns: :class:`Directory` or raises an exception. - :exception: + :raises: :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR If path is not relative database or absolute with initial components same as database. @@ -410,7 +410,7 @@ class Database(object): :rtype: 2-tuple(:class:`Message`, :attr:`STATUS`) - :exception: Raises a :exc:`NotmuchError` with the following meaning. + :raises: Raises a :exc:`NotmuchError` with the following meaning. If such an exception occurs, nothing was added to the database. :attr:`STATUS`.FILE_ERROR @@ -460,7 +460,7 @@ class Database(object): This filename was removed but the message persists in the database with at least one other filename. - :exception: Raises a :exc:`NotmuchError` with the following meaning. + :raises: Raises a :exc:`NotmuchError` with the following meaning. If such an exception occurs, nothing was removed from the database. @@ -479,7 +479,7 @@ class Database(object): :param msgid: The message ID :type msgid: unicode or str :returns: :class:`Message` or `None` if no message is found. - :exception: + :raises: :exc:`OutOfMemoryError` If an Out-of-memory occured while constructing the message. :exc:`XapianError` @@ -512,7 +512,7 @@ class Database(object): function returns None if no message is found with the given filename. - :exception: + :raises: :exc:`OutOfMemoryError` If an Out-of-memory occured while constructing the message. :exc:`XapianError` @@ -681,7 +681,7 @@ class Directory(object): :param mtime: A (time_t) timestamp :returns: Nothing on success, raising an exception on failure. - :exception: :exc:`NotmuchError`: + :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION A Xapian exception occurred, mtime not stored. @@ -708,7 +708,7 @@ class Directory(object): :param mtime: A (time_t) timestamp :returns: Nothing on success, raising an exception on failure. - :exception: :exc:`NotmuchError`: + :raises: :exc:`NotmuchError`: :attr:`STATUS`.NOT_INITIALIZED The directory has not been initialized diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index b291f9f..ce7cb88 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -370,7 +370,7 @@ class Message(Python3StringMixIn): """Returns the message ID :returns: String with a message ID - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: @@ -387,7 +387,7 @@ class Message(Python3StringMixIn): message belongs to a single thread. :returns: String with a thread ID - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: @@ -410,7 +410,7 @@ class Message(Python3StringMixIn): an empty Messages iterator. :returns: :class:`Messages`. - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: @@ -432,7 +432,7 @@ class Message(Python3StringMixIn): :returns: A time_t timestamp. :rtype: c_unit64 - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: @@ -469,7 +469,7 @@ class Message(Python3StringMixIn): """Returns the file path of the message file :returns: Absolute file path & name of the message file - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: @@ -499,7 +499,7 @@ class Message(Python3StringMixIn): :param flag: One of the :attr:`Message.FLAG` values (currently only *Message.FLAG.MATCH* :returns: An unsigned int (0/1), indicating whether the flag is set. - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: @@ -514,7 +514,7 @@ class Message(Python3StringMixIn): :param value: A bool indicating whether to set or unset the flag. :returns: Nothing - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index 6132ca0..25b9e78 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -89,7 +89,7 @@ class Query(object): :param querystr: The query string :type querystr: utf-8 encoded str or unicode :returns: Nothing - :exception: + :raises: :exc:`NullPointerError` if the query creation failed (e.g. too little memory). :exc:`NotInitializedError` if the underlying db was not @@ -134,7 +134,7 @@ class Query(object): to get the value of this flag. :returns: :class:`Threads` - :exception: :exc:`NullPointerError` if search_threads failed + :raises: :exc:`NullPointerError` if search_threads failed """ self._assert_query_is_initialized() threads_p = Query._search_threads(self._query) @@ -153,7 +153,7 @@ class Query(object): :class:`Messages` in the defined sort order :returns: :class:`Messages` - :exception: :exc:`NullPointerError` if search_messages failed + :raises: :exc:`NullPointerError` if search_messages failed """ self._assert_query_is_initialized() msgs_p = Query._search_messages(self._query) diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index 5c58028..d1ba3e5 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -248,7 +248,7 @@ class Thread(object): for as long as the thread is valid. :returns: String with a message ID - :exception: :exc:`NotInitializedError` if the thread + :raises: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: @@ -265,7 +265,7 @@ class Thread(object): :returns: The number of all messages in the database belonging to this thread. Contrast with :meth:`get_matched_messages`. - :exception: :exc:`NotInitializedError` if the thread + :raises: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: @@ -309,7 +309,7 @@ class Thread(object): :returns: The number of all messages belonging to this thread that matched the :class:`Query`from which this thread was created. Contrast with :meth:`get_total_messages`. - :exception: :exc:`NotInitializedError` if the thread + :raises: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: @@ -351,7 +351,7 @@ class Thread(object): :returns: A time_t timestamp. :rtype: c_unit64 - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._thread: @@ -363,7 +363,7 @@ class Thread(object): :returns: A time_t timestamp. :rtype: c_unit64 - :exception: :exc:`NotInitializedError` if the message + :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._thread: -- cgit v1.2.3 From 1737ff5290a8ce1f66b4a7aeb6a16146dbc3517c Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Tue, 21 Feb 2012 00:13:20 +0100 Subject: python: rework Directory.set_mtime Fix the indentation within the docstring, remove useless remarks, do some trivial refactoring. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 42a4442..9f6b380 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -679,27 +679,19 @@ class Directory(object): don't store a timestamp of 0 unless you are comfortable with that. - :param mtime: A (time_t) timestamp - :returns: Nothing on success, raising an exception on failure. - :raises: :exc:`NotmuchError`: - - :attr:`STATUS`.XAPIAN_EXCEPTION - A Xapian exception occurred, mtime not stored. - :attr:`STATUS`.READ_ONLY_DATABASE - Database was opened in read-only mode so directory - mtime cannot be modified. - :attr:`STATUS`.NOT_INITIALIZED - The directory has not been initialized + :param mtime: A (time_t) timestamp + :raises: :exc:`XapianError` a Xapian exception occurred, mtime + not stored + :raises: :exc:`ReadOnlyDatabaseError` the database was opened + in read-only mode so directory mtime cannot be modified + :raises: :exc:`NotInitializedError` the directory object has not + been initialized """ self._assert_dir_is_initialized() - #TODO: make sure, we convert the mtime parameter to a 'c_long' status = Directory._set_mtime(self._dir_p, mtime) - #return on success - if status == STATUS.SUCCESS: - return - #fail with Exception otherwise - raise NotmuchError(status) + if status != STATUS.SUCCESS: + raise NotmuchError(status) def get_mtime(self): """Gets the mtime value of this directory in the database -- cgit v1.2.3 From 786f9882e8b408e6ad4c6b7abfef1ac54144be15 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Tue, 21 Feb 2012 00:15:59 +0100 Subject: python: remove :returns: keywords from functions returning nothing Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 3 --- bindings/python/notmuch/message.py | 1 - bindings/python/notmuch/query.py | 1 - 3 files changed, 5 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 9f6b380..d841367 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -176,7 +176,6 @@ class Database(object): :param path: A directory in which we should create the database. :type path: str - :returns: Nothing :raises: :exc:`NotmuchError` in case of any failure (possibly after printing an error message on stderr). """ @@ -200,7 +199,6 @@ class Database(object): :param status: Open the database in read-only or read-write mode :type status: :attr:`MODE` - :returns: Nothing :raises: Raises :exc:`NotmuchError` in case of any failure (possibly after printing an error message on stderr). """ @@ -699,7 +697,6 @@ class Directory(object): Retrieves a previously stored mtime for this directory. :param mtime: A (time_t) timestamp - :returns: Nothing on success, raising an exception on failure. :raises: :exc:`NotmuchError`: :attr:`STATUS`.NOT_INITIALIZED diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index ce7cb88..9ebb53d 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -513,7 +513,6 @@ class Message(Python3StringMixIn): *Message.FLAG.MATCH* :param value: A bool indicating whether to set or unset the flag. - :returns: Nothing :raises: :exc:`NotInitializedError` if the message is not initialized. """ diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index 25b9e78..15ed153 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -88,7 +88,6 @@ class Query(object): :type db: :class:`Database` :param querystr: The query string :type querystr: utf-8 encoded str or unicode - :returns: Nothing :raises: :exc:`NullPointerError` if the query creation failed (e.g. too little memory). -- cgit v1.2.3 From c1094bc2d767159b5478cd0399d37a4813d0de05 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Tue, 21 Feb 2012 00:56:07 +0100 Subject: python: allow an empty path as parameter to Database.get_directory Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index d841367..ab3cdec 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -351,7 +351,7 @@ class Database(object): """ self._assert_db_is_initialized() # sanity checking if path is valid, and make path absolute - if path[0] == os.sep: + if path and path[0] == os.sep: # we got an absolute path if not path.startswith(self.get_path()): # but its initial components are not equal to the db path -- cgit v1.2.3 From 0b2ff308ece7e45a32a9e5a98d400b268278071a Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Tue, 21 Feb 2012 01:06:15 +0100 Subject: python: fix the type nonsense of the first parameter of class Directory Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index ab3cdec..504bb88 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -365,7 +365,7 @@ class Database(object): dir_p = Database._get_directory(self._db, _str(path)) # return the Directory, init it with the absolute path - return Directory(_str(abs_dirpath), dir_p, self) + return Directory(abs_dirpath, dir_p, self) _add_message = nmlib.notmuch_database_add_message _add_message.argtypes = [NotmuchDatabaseP, c_char_p, @@ -639,7 +639,7 @@ class Directory(object): def __init__(self, path, dir_p, parent): """ - :param path: The absolute path of the directory object as unicode. + :param path: The absolute path of the directory object. :param dir_p: The pointer to an internal notmuch_directory_t object. :param parent: The object this Directory is derived from (usually a :class:`Database`). We do not directly use @@ -647,7 +647,6 @@ class Directory(object): this Directory object lives. This keeps the parent object alive. """ - assert isinstance(path, unicode), "Path needs to be an UNICODE object" self._path = path self._dir_p = dir_p self._parent = parent -- cgit v1.2.3 From e2e95caa51f8c1ea57b4fc7d3926fda1fb1aed10 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sun, 19 Feb 2012 00:56:57 +0100 Subject: Prevent segmentation fault in notmuch_database_close Previously opening a notmuch database in read write mode that has been locked resulted in the notmuch_database_open function executing notmuch_database_close as a cleanup function. notmuch_database_close failed to check whether the xapian database has in fact been created. Add a check whether the xapian database object has actually been created before trying to call its flush method. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- lib/database.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/database.cc b/lib/database.cc index c928d02..5efa85e 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -716,7 +716,8 @@ void notmuch_database_close (notmuch_database_t *notmuch) { try { - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE) + if (notmuch->xapian_db != NULL && + notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE) (static_cast (notmuch->xapian_db))->flush (); } catch (const Xapian::Error &error) { if (! notmuch->exception_reported) { -- cgit v1.2.3 From 311e8f6c4562298b4e826ce131597e085ece69d3 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 13:02:40 -0500 Subject: emacs: Fix out-of-date declare-function The names of the arguments to notmuch-show-refresh-view had gotten out of sync between the declare-function and the real thing. --- emacs/notmuch-crypto.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index c7ef1eb..972f26e 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -120,7 +120,7 @@ mode." :notmuch-from from) (insert "\n"))) -(declare-function notmuch-show-refresh-view "notmuch-show" (&optional crypto-switch)) +(declare-function notmuch-show-refresh-view "notmuch-show" (&optional retain-state)) (defun notmuch-crypto-sigstatus-good-callback (button) (let* ((sigstatus (button-get button :notmuch-sigstatus)) -- cgit v1.2.3 From e3fb62f59b7cf93c818f382a62c37eea2d1b4f74 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Wed, 18 Jan 2012 13:19:41 +0100 Subject: test: always report missing prereqs, independent of `--verbose' option When tests are skipped due to missing prereqs, those prereqs are only displayed when running with the `--verbose' option. This is essential information when troubleshooting, so always send to stdout. --- test/test-lib.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 0174e93..063a2b2 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -702,8 +702,8 @@ test_skip () { test_check_missing_external_prereqs_ () { if test -n "$test_subtest_missing_external_prereqs_"; then - say_color skip >&3 "missing prerequisites:" - echo "$test_subtest_missing_external_prereqs_" >&3 + say_color skip >&1 "missing prerequisites:" + echo "$test_subtest_missing_external_prereqs_" >&1 test_report_skip_ "$@" else false -- cgit v1.2.3 From 92983dd14e3c9d767bb8ef6e9b85ef76b293cdd4 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 21:01:24 +0100 Subject: python: avoid using a magic value for database mode in Database.__init__ Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 504bb88..c905395 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -122,7 +122,8 @@ class Database(object): _create.argtypes = [c_char_p] _create.restype = NotmuchDatabaseP - def __init__(self, path=None, create=False, mode=0): + def __init__(self, path = None, create = False, + mode = MODE.READ_ONLY): """If *path* is `None`, we will try to read a users notmuch configuration and use his configured database. The location of the configuration file can be specified through the environment variable -- cgit v1.2.3 From 35ceaf496fe2d2a1324a8462e629e618ed20037a Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 20:46:42 +0100 Subject: python: Improve the docstring of Database.get_directory Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index c905395..bef9720 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -345,10 +345,8 @@ class Database(object): of database (see :meth:`get_path`), or else should be an absolute path with initial components that match the path of 'database'. :returns: :class:`Directory` or raises an exception. - :raises: - :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR - If path is not relative database or absolute with initial - components same as database. + :raises: :exc:`FileError` if path is not relative database or absolute + with initial components same as database. """ self._assert_db_is_initialized() # sanity checking if path is valid, and make path absolute -- cgit v1.2.3 From fcf19ad029913e88558a21135feb0b5e1b33cef3 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 20:58:44 +0100 Subject: python: work around libnotmuch calling exit(3) in Database.get_directory Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index bef9720..82cb803 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -27,6 +27,7 @@ from notmuch.globals import ( NotmuchError, NullPointerError, NotInitializedError, + ReadOnlyDatabaseError, Enum, _str, NotmuchDatabaseP, @@ -145,6 +146,7 @@ class Database(object): failure. """ self._db = None + self.mode = mode if path is None: # no path specified. use a user's default database if Database._std_db_path is None: @@ -335,20 +337,24 @@ class Database(object): """Returns a :class:`Directory` of path, (creating it if it does not exist(?)) - .. warning:: - - This call needs a writeable database in - :attr:`Database.MODE`.READ_WRITE mode. The underlying library will - exit the program if this method is used on a read-only database! - :param path: An unicode string containing the path relative to the path of database (see :meth:`get_path`), or else should be an absolute path with initial components that match the path of 'database'. :returns: :class:`Directory` or raises an exception. :raises: :exc:`FileError` if path is not relative database or absolute with initial components same as database. + :raises: :exc:`ReadOnlyDatabaseError` if the database has not been + opened in read-write mode """ self._assert_db_is_initialized() + + # work around libnotmuch calling exit(3), see + # id:20120221002921.8534.57091@thinkbox.jade-hamburg.de + # TODO: remove once this issue is resolved + if self.mode != Database.MODE.READ_WRITE: + raise ReadOnlyDatabaseError('The database has to be opened in ' + 'read-write mode for get_directory') + # sanity checking if path is valid, and make path absolute if path and path[0] == os.sep: # we got an absolute path -- cgit v1.2.3 From 05cdb3d7b7c007364fe9fa38ff36488feaf698b7 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 21:07:18 +0100 Subject: python: improve the docstring of Database.find_message_by_filename Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 82cb803..ebb8f20 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -515,17 +515,16 @@ class Database(object): function returns None if no message is found with the given filename. - :raises: - :exc:`OutOfMemoryError` - If an Out-of-memory occured while constructing the message. - :exc:`XapianError` - In case of a Xapian Exception. These exceptions - include "Database modified" situations, e.g. when the - notmuch database has been modified by another program - in the meantime. In this case, you should close and - reopen the database and retry. - :exc:`NotInitializedError` if - the database was not intitialized. + :raises: :exc:`OutOfMemoryError` if an Out-of-memory occured while + constructing the message. + :raises: :exc:`XapianError` in case of a Xapian Exception. + These exceptions include "Database modified" + situations, e.g. when the notmuch database has been + modified by another program in the meantime. In this + case, you should close and reopen the database and + retry. + :raises: :exc:`NotInitializedError` if the database was not + intitialized. *Added in notmuch 0.9*""" self._assert_db_is_initialized() -- cgit v1.2.3 From 1736488ecfd9b18a380ce04ac2df0303c0ea3c80 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 21:14:13 +0100 Subject: python: work around libnotmuch calling exit(3) in Database.find_message_by_filename Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/database.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index ebb8f20..a054be7 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -504,12 +504,6 @@ class Database(object): def find_message_by_filename(self, filename): """Find a message with the given filename - .. warning:: - - This call needs a writeable database in - :attr:`Database.MODE`.READ_WRITE mode. The underlying library will - exit the program if this method is used on a read-only database! - :returns: If the database contains a message with the given filename, then a class:`Message:` is returned. This function returns None if no message is found with the given @@ -525,9 +519,19 @@ class Database(object): retry. :raises: :exc:`NotInitializedError` if the database was not intitialized. + :raises: :exc:`ReadOnlyDatabaseError` if the database has not been + opened in read-write mode *Added in notmuch 0.9*""" self._assert_db_is_initialized() + + # work around libnotmuch calling exit(3), see + # id:20120221002921.8534.57091@thinkbox.jade-hamburg.de + # TODO: remove once this issue is resolved + if self.mode != Database.MODE.READ_WRITE: + raise ReadOnlyDatabaseError('The database has to be opened in ' + 'read-write mode for get_directory') + msg_p = NotmuchMessageP() status = Database._find_message_by_filename(self._db, _str(filename), byref(msg_p)) -- cgit v1.2.3 From ba95980cf1a5e2b32104611ccdf2e9c43bf3305a Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 21:55:59 +0100 Subject: python: refactor the python bindings Move the Directory class into its own file, merge the two Filenames classes into one, deprecate Filenames.as_iterator, update the documentation accordingly. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 20 +-- bindings/python/notmuch/__init__.py | 15 ++- bindings/python/notmuch/database.py | 239 +--------------------------------- bindings/python/notmuch/directory.py | 183 ++++++++++++++++++++++++++ bindings/python/notmuch/filename.py | 62 +++++++-- 5 files changed, 256 insertions(+), 263 deletions(-) create mode 100644 bindings/python/notmuch/directory.py diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index 25eb146..e87a865 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -254,26 +254,28 @@ More information on specific topics can be found on the following pages: :class:`Filenames` -- An iterator over filenames ------------------------------------------------ -.. autoclass:: notmuch.database.Filenames +.. autoclass:: notmuch.Filenames - .. automethod:: notmuch.database.Filenames.__len__ + .. automethod:: notmuch.Filenames.__len__ + + .. automethod:: notmuch.Filenames.as_generator :class:`notmuch.database.Directoy` -- A directory entry in the database ------------------------------------------------------------------------ -.. autoclass:: notmuch.database.Directory +.. autoclass:: notmuch.Directory - .. automethod:: notmuch.database.Directory.get_child_files + .. automethod:: notmuch.Directory.get_child_files - .. automethod:: notmuch.database.Directory.get_child_directories + .. automethod:: notmuch.Directory.get_child_directories - .. automethod:: notmuch.database.Directory.get_mtime + .. automethod:: notmuch.Directory.get_mtime - .. automethod:: notmuch.database.Directory.set_mtime + .. automethod:: notmuch.Directory.set_mtime - .. autoattribute:: notmuch.database.Directory.mtime + .. autoattribute:: notmuch.Directory.mtime - .. autoattribute:: notmuch.database.Directory.path + .. autoattribute:: notmuch.Directory.path The `next page `_ contains information on possible Status and Error values. diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index f3ff987..8de73d5 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -51,11 +51,14 @@ along with notmuch. If not, see . Copyright 2010-2011 Sebastian Spaeth """ -from notmuch.database import Database, Query -from notmuch.message import Messages, Message -from notmuch.thread import Threads, Thread -from notmuch.tag import Tags -from notmuch.globals import ( +from .database import Database +from .directory import Directory +from .filename import Filenames +from .message import Messages, Message +from .query import Query +from .tag import Tags +from .thread import Threads, Thread +from .globals import ( nmlib, STATUS, NotmuchError, @@ -71,6 +74,6 @@ from notmuch.globals import ( UnbalancedAtomicError, NotInitializedError, ) -from notmuch.version import __VERSION__ +from .version import __VERSION__ __LICENSE__ = "GPL v3+" __AUTHOR__ = 'Sebastian Spaeth ' diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index a054be7..800264b 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -19,7 +19,7 @@ Copyright 2010 Sebastian Spaeth ' import os import codecs -from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER +from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER from notmuch.globals import ( nmlib, STATUS, @@ -34,11 +34,11 @@ from notmuch.globals import ( NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP, - NotmuchFilenamesP ) from notmuch.message import Message from notmuch.tag import Tags from .query import Query +from .directory import Directory class Database(object): """The :class:`Database` is the highest-level object that notmuch @@ -603,238 +603,3 @@ class Database(object): guaranteed to remain stable in future versions). """ return self._db - - -class Directory(object): - """Represents a directory entry in the notmuch directory - - Modifying attributes of this object will modify the - database, not the real directory attributes. - - The Directory object is usually derived from another object - e.g. via :meth:`Database.get_directory`, and will automatically be - become invalid whenever that parent is deleted. You should - therefore initialized this object handing it a reference to the - parent, preventing the parent from automatically being garbage - collected. - """ - - """notmuch_directory_get_mtime""" - _get_mtime = nmlib.notmuch_directory_get_mtime - _get_mtime.argtypes = [NotmuchDirectoryP] - _get_mtime.restype = c_long - - """notmuch_directory_set_mtime""" - _set_mtime = nmlib.notmuch_directory_set_mtime - _set_mtime.argtypes = [NotmuchDirectoryP, c_long] - _set_mtime.restype = c_uint - - """notmuch_directory_get_child_files""" - _get_child_files = nmlib.notmuch_directory_get_child_files - _get_child_files.argtypes = [NotmuchDirectoryP] - _get_child_files.restype = NotmuchFilenamesP - - """notmuch_directory_get_child_directories""" - _get_child_directories = nmlib.notmuch_directory_get_child_directories - _get_child_directories.argtypes = [NotmuchDirectoryP] - _get_child_directories.restype = NotmuchFilenamesP - - def _assert_dir_is_initialized(self): - """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) - if dir_p is None""" - if not self._dir_p: - raise NotInitializedError() - - def __init__(self, path, dir_p, parent): - """ - :param path: The absolute path of the directory object. - :param dir_p: The pointer to an internal notmuch_directory_t object. - :param parent: The object this Directory is derived from - (usually a :class:`Database`). We do not directly use - this, but store a reference to it as long as - this Directory object lives. This keeps the - parent object alive. - """ - self._path = path - self._dir_p = dir_p - self._parent = parent - - def set_mtime(self, mtime): - """Sets the mtime value of this directory in the database - - The intention is for the caller to use the mtime to allow efficient - identification of new messages to be added to the database. The - recommended usage is as follows: - - * Read the mtime of a directory from the filesystem - - * Call :meth:`Database.add_message` for all mail files in - the directory - - * Call notmuch_directory_set_mtime with the mtime read from the - filesystem. Then, when wanting to check for updates to the - directory in the future, the client can call :meth:`get_mtime` - and know that it only needs to add files if the mtime of the - directory and files are newer than the stored timestamp. - - .. note:: - - :meth:`get_mtime` function does not allow the caller to - distinguish a timestamp of 0 from a non-existent timestamp. So - don't store a timestamp of 0 unless you are comfortable with - that. - - :param mtime: A (time_t) timestamp - :raises: :exc:`XapianError` a Xapian exception occurred, mtime - not stored - :raises: :exc:`ReadOnlyDatabaseError` the database was opened - in read-only mode so directory mtime cannot be modified - :raises: :exc:`NotInitializedError` the directory object has not - been initialized - """ - self._assert_dir_is_initialized() - status = Directory._set_mtime(self._dir_p, mtime) - - if status != STATUS.SUCCESS: - raise NotmuchError(status) - - def get_mtime(self): - """Gets the mtime value of this directory in the database - - Retrieves a previously stored mtime for this directory. - - :param mtime: A (time_t) timestamp - :raises: :exc:`NotmuchError`: - - :attr:`STATUS`.NOT_INITIALIZED - The directory has not been initialized - """ - self._assert_dir_is_initialized() - return Directory._get_mtime(self._dir_p) - - # Make mtime attribute a property of Directory() - mtime = property(get_mtime, set_mtime, doc="""Property that allows getting - and setting of the Directory *mtime* (read-write) - - See :meth:`get_mtime` and :meth:`set_mtime` for usage and - possible exceptions.""") - - def get_child_files(self): - """Gets a Filenames iterator listing all the filenames of - messages in the database within the given directory. - - The returned filenames will be the basename-entries only (not - complete paths. - """ - self._assert_dir_is_initialized() - files_p = Directory._get_child_files(self._dir_p) - return Filenames(files_p, self) - - def get_child_directories(self): - """Gets a :class:`Filenames` iterator listing all the filenames of - sub-directories in the database within the given directory - - The returned filenames will be the basename-entries only (not - complete paths. - """ - self._assert_dir_is_initialized() - files_p = Directory._get_child_directories(self._dir_p) - return Filenames(files_p, self) - - @property - def path(self): - """Returns the absolute path of this Directory (read-only)""" - return self._path - - def __repr__(self): - """Object representation""" - return "" % self._path - - _destroy = nmlib.notmuch_directory_destroy - _destroy.argtypes = [NotmuchDirectoryP] - _destroy.argtypes = None - - def __del__(self): - """Close and free the Directory""" - if self._dir_p is not None: - self._destroy(self._dir_p) - - -class Filenames(object): - """An iterator over File- or Directory names stored in the database""" - - #notmuch_filenames_get - _get = nmlib.notmuch_filenames_get - _get.argtypes = [NotmuchFilenamesP] - _get.restype = c_char_p - - def __init__(self, files_p, parent): - """ - :param files_p: The pointer to an internal notmuch_filenames_t object. - :param parent: The object this Directory is derived from - (usually a Directory()). We do not directly use - this, but store a reference to it as long as - this Directory object lives. This keeps the - parent object alive. - """ - self._files_p = files_p - self._parent = parent - - def __iter__(self): - """ Make Filenames an iterator """ - return self - - _valid = nmlib.notmuch_filenames_valid - _valid.argtypes = [NotmuchFilenamesP] - _valid.restype = bool - - _move_to_next = nmlib.notmuch_filenames_move_to_next - _move_to_next.argtypes = [NotmuchFilenamesP] - _move_to_next.restype = None - - def __next__(self): - if not self._files_p: - raise NotInitializedError() - - if not self._valid(self._files_p): - self._files_p = None - raise StopIteration - - file_ = Filenames._get(self._files_p) - self._move_to_next(self._files_p) - return file_.decode('utf-8', 'ignore') - next = __next__ # python2.x iterator protocol compatibility - - def __len__(self): - """len(:class:`Filenames`) returns the number of contained files - - .. note:: - - As this iterates over the files, we will not be able to - iterate over them again! So this will fail:: - - #THIS FAILS - files = Database().get_directory('').get_child_files() - if len(files) > 0: # this 'exhausts' msgs - # next line raises - # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) - for file in files: print file - """ - if not self._files_p: - raise NotInitializedError() - - i = 0 - while self._valid(self._files_p): - self._move_to_next(self._files_p) - i += 1 - self._files_p = None - return i - - _destroy = nmlib.notmuch_filenames_destroy - _destroy.argtypes = [NotmuchFilenamesP] - _destroy.restype = None - - def __del__(self): - """Close and free Filenames""" - if self._files_p is not None: - self._destroy(self._files_p) diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py new file mode 100644 index 0000000..3e0763f --- /dev/null +++ b/bindings/python/notmuch/directory.py @@ -0,0 +1,183 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth ' +""" + +from ctypes import c_uint, c_long +from notmuch.globals import ( + nmlib, + STATUS, + NotmuchError, + NotInitializedError, + NotmuchDirectoryP, + NotmuchFilenamesP +) +from .filename import Filenames + +class Directory(object): + """Represents a directory entry in the notmuch directory + + Modifying attributes of this object will modify the + database, not the real directory attributes. + + The Directory object is usually derived from another object + e.g. via :meth:`Database.get_directory`, and will automatically be + become invalid whenever that parent is deleted. You should + therefore initialized this object handing it a reference to the + parent, preventing the parent from automatically being garbage + collected. + """ + + """notmuch_directory_get_mtime""" + _get_mtime = nmlib.notmuch_directory_get_mtime + _get_mtime.argtypes = [NotmuchDirectoryP] + _get_mtime.restype = c_long + + """notmuch_directory_set_mtime""" + _set_mtime = nmlib.notmuch_directory_set_mtime + _set_mtime.argtypes = [NotmuchDirectoryP, c_long] + _set_mtime.restype = c_uint + + """notmuch_directory_get_child_files""" + _get_child_files = nmlib.notmuch_directory_get_child_files + _get_child_files.argtypes = [NotmuchDirectoryP] + _get_child_files.restype = NotmuchFilenamesP + + """notmuch_directory_get_child_directories""" + _get_child_directories = nmlib.notmuch_directory_get_child_directories + _get_child_directories.argtypes = [NotmuchDirectoryP] + _get_child_directories.restype = NotmuchFilenamesP + + def _assert_dir_is_initialized(self): + """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) + if dir_p is None""" + if not self._dir_p: + raise NotInitializedError() + + def __init__(self, path, dir_p, parent): + """ + :param path: The absolute path of the directory object. + :param dir_p: The pointer to an internal notmuch_directory_t object. + :param parent: The object this Directory is derived from + (usually a :class:`Database`). We do not directly use + this, but store a reference to it as long as + this Directory object lives. This keeps the + parent object alive. + """ + self._path = path + self._dir_p = dir_p + self._parent = parent + + def set_mtime(self, mtime): + """Sets the mtime value of this directory in the database + + The intention is for the caller to use the mtime to allow efficient + identification of new messages to be added to the database. The + recommended usage is as follows: + + * Read the mtime of a directory from the filesystem + + * Call :meth:`Database.add_message` for all mail files in + the directory + + * Call notmuch_directory_set_mtime with the mtime read from the + filesystem. Then, when wanting to check for updates to the + directory in the future, the client can call :meth:`get_mtime` + and know that it only needs to add files if the mtime of the + directory and files are newer than the stored timestamp. + + .. note:: + + :meth:`get_mtime` function does not allow the caller to + distinguish a timestamp of 0 from a non-existent timestamp. So + don't store a timestamp of 0 unless you are comfortable with + that. + + :param mtime: A (time_t) timestamp + :raises: :exc:`XapianError` a Xapian exception occurred, mtime + not stored + :raises: :exc:`ReadOnlyDatabaseError` the database was opened + in read-only mode so directory mtime cannot be modified + :raises: :exc:`NotInitializedError` the directory object has not + been initialized + """ + self._assert_dir_is_initialized() + status = Directory._set_mtime(self._dir_p, mtime) + + if status != STATUS.SUCCESS: + raise NotmuchError(status) + + def get_mtime(self): + """Gets the mtime value of this directory in the database + + Retrieves a previously stored mtime for this directory. + + :param mtime: A (time_t) timestamp + :raises: :exc:`NotmuchError`: + + :attr:`STATUS`.NOT_INITIALIZED + The directory has not been initialized + """ + self._assert_dir_is_initialized() + return Directory._get_mtime(self._dir_p) + + # Make mtime attribute a property of Directory() + mtime = property(get_mtime, set_mtime, doc="""Property that allows getting + and setting of the Directory *mtime* (read-write) + + See :meth:`get_mtime` and :meth:`set_mtime` for usage and + possible exceptions.""") + + def get_child_files(self): + """Gets a Filenames iterator listing all the filenames of + messages in the database within the given directory. + + The returned filenames will be the basename-entries only (not + complete paths. + """ + self._assert_dir_is_initialized() + files_p = Directory._get_child_files(self._dir_p) + return Filenames(files_p, self) + + def get_child_directories(self): + """Gets a :class:`Filenames` iterator listing all the filenames of + sub-directories in the database within the given directory + + The returned filenames will be the basename-entries only (not + complete paths. + """ + self._assert_dir_is_initialized() + files_p = Directory._get_child_directories(self._dir_p) + return Filenames(files_p, self) + + @property + def path(self): + """Returns the absolute path of this Directory (read-only)""" + return self._path + + def __repr__(self): + """Object representation""" + return "" % self._path + + _destroy = nmlib.notmuch_directory_destroy + _destroy.argtypes = [NotmuchDirectoryP] + _destroy.argtypes = None + + def __del__(self): + """Close and free the Directory""" + if self._dir_p is not None: + self._destroy(self._dir_p) diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index 353eb76..232a9ed 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -78,10 +78,14 @@ class Filenames(Python3StringMixIn): if not files_p: raise NullPointerError() - self._files = files_p + self._files_p = files_p #save reference to parent object so we keep it alive self._parent = parent + def __iter__(self): + """ Make Filenames an iterator """ + return self + _valid = nmlib.notmuch_filenames_valid _valid.argtypes = [NotmuchFilenamesP] _valid.restype = bool @@ -90,19 +94,30 @@ class Filenames(Python3StringMixIn): _move_to_next.argtypes = [NotmuchFilenamesP] _move_to_next.restype = None + def __next__(self): + if not self._files_p: + raise NotInitializedError() + + if not self._valid(self._files_p): + self._files_p = None + raise StopIteration + + file_ = Filenames._get(self._files_p) + self._move_to_next(self._files_p) + return file_.decode('utf-8', 'ignore') + next = __next__ # python2.x iterator protocol compatibility + def as_generator(self): """Return generator of Filenames This is the main function that will usually be used by the - user.""" - if not self._files: - raise NotInitializedError() + user. - while self._valid(self._files): - yield Filenames._get(self._files).decode('utf-8', 'ignore') - self._move_to_next(self._files) - - self._files = None + .. deprecated:: 0.12 + :class:`Filenames` objects implement the + iterator protocol. + """ + return self def __unicode__(self): """Represent Filenames() as newline-separated list of full paths @@ -123,5 +138,30 @@ class Filenames(Python3StringMixIn): def __del__(self): """Close and free the notmuch filenames""" - if self._files is not None: - self._destroy(self._files) + if self._files_p is not None: + self._destroy(self._files_p) + + def __len__(self): + """len(:class:`Filenames`) returns the number of contained files + + .. note:: + + As this iterates over the files, we will not be able to + iterate over them again! So this will fail:: + + #THIS FAILS + files = Database().get_directory('').get_child_files() + if len(files) > 0: # this 'exhausts' msgs + # next line raises + # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) + for file in files: print file + """ + if not self._files_p: + raise NotInitializedError() + + i = 0 + while self._valid(self._files_p): + self._move_to_next(self._files_p) + i += 1 + self._files_p = None + return i -- cgit v1.2.3 From 69f077898a65c10453d08dd94bf4c94efc69b91b Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 22:34:40 +0100 Subject: python: move Messages class into its own file Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/__init__.py | 3 +- bindings/python/notmuch/message.py | 242 +-------------------------------- bindings/python/notmuch/messages.py | 262 ++++++++++++++++++++++++++++++++++++ bindings/python/notmuch/query.py | 2 +- bindings/python/notmuch/thread.py | 2 +- 5 files changed, 272 insertions(+), 239 deletions(-) create mode 100644 bindings/python/notmuch/messages.py diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index 8de73d5..b8f3610 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -54,7 +54,8 @@ Copyright 2010-2011 Sebastian Spaeth from .database import Database from .directory import Directory from .filename import Filenames -from .message import Messages, Message +from .message import Message +from .messages import Messages from .query import Query from .tag import Tags from .thread import Threads, Thread diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 9ebb53d..20ba9cb 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -21,7 +21,7 @@ Copyright 2010 Sebastian Spaeth ' from ctypes import c_char_p, c_long, c_uint, c_int from datetime import date -from notmuch.globals import ( +from .globals import ( nmlib, Enum, _str, @@ -35,9 +35,9 @@ from notmuch.globals import ( NotmuchMessagesP, NotmuchFilenamesP, ) -from notmuch.tag import Tags -from notmuch.filename import Filenames -import sys +from .tag import Tags +from .filename import Filenames + import email try: import simplejson as json @@ -45,238 +45,6 @@ except ImportError: import json -class Messages(object): - """Represents a list of notmuch messages - - This object provides an iterator over a list of notmuch messages - (Technically, it provides a wrapper for the underlying - *notmuch_messages_t* structure). Do note that the underlying library - only provides a one-time iterator (it cannot reset the iterator to - the start). Thus iterating over the function will "exhaust" the list - of messages, and a subsequent iteration attempt will raise a - :exc:`NotInitializedError`. If you need to - re-iterate over a list of messages you will need to retrieve a new - :class:`Messages` object or cache your :class:`Message`\s in a list - via:: - - msglist = list(msgs) - - You can store and reuse the single :class:`Message` objects as often - as you want as long as you keep the parent :class:`Messages` object - around. (Due to hierarchical memory allocation, all derived - :class:`Message` objects will be invalid when we delete the parent - :class:`Messages` object, even if it was already exhausted.) So - this works:: - - db = Database() - msgs = Query(db,'').search_messages() #get a Messages() object - msglist = list(msgs) - - # msgs is "exhausted" now and msgs.next() will raise an exception. - # However it will be kept alive until all retrieved Message() - # objects are also deleted. If you do e.g. an explicit del(msgs) - # here, the following lines would fail. - - # You can reiterate over *msglist* however as often as you want. - # It is simply a list with :class:`Message`s. - - print (msglist[0].get_filename()) - print (msglist[1].get_filename()) - print (msglist[0].get_message_id()) - - - As :class:`Message` implements both __hash__() and __cmp__(), it is - possible to make sets out of :class:`Messages` and use set - arithmetic (this happens in python and will of course be *much* - slower than redoing a proper query with the appropriate filters:: - - s1, s2 = set(msgs1), set(msgs2) - s.union(s2) - s1 -= s2 - ... - - Be careful when using set arithmetic between message sets derived - from different Databases (ie the same database reopened after - messages have changed). If messages have added or removed associated - files in the meantime, it is possible that the same message would be - considered as a different object (as it points to a different file). - """ - - #notmuch_messages_get - _get = nmlib.notmuch_messages_get - _get.argtypes = [NotmuchMessagesP] - _get.restype = NotmuchMessageP - - _collect_tags = nmlib.notmuch_messages_collect_tags - _collect_tags.argtypes = [NotmuchMessagesP] - _collect_tags.restype = NotmuchTagsP - - def __init__(self, msgs_p, parent=None): - """ - :param msgs_p: A pointer to an underlying *notmuch_messages_t* - structure. These are not publically exposed, so a user - will almost never instantiate a :class:`Messages` object - herself. They are usually handed back as a result, - e.g. in :meth:`Query.search_messages`. *msgs_p* must be - valid, we will raise an :exc:`NullPointerError` if it is - `None`. - :type msgs_p: :class:`ctypes.c_void_p` - :param parent: The parent object - (ie :class:`Query`) these tags are derived from. It saves - a reference to it, so we can automatically delete the db - object once all derived objects are dead. - :TODO: Make the iterator work more than once and cache the tags in - the Python object.(?) - """ - if not msgs_p: - raise NullPointerError() - - self._msgs = msgs_p - #store parent, so we keep them alive as long as self is alive - self._parent = parent - - def collect_tags(self): - """Return the unique :class:`Tags` in the contained messages - - :returns: :class:`Tags` - :exceptions: :exc:`NotInitializedError` if not init'ed - - .. note:: - - :meth:`collect_tags` will iterate over the messages and therefore - will not allow further iterations. - """ - if not self._msgs: - raise NotInitializedError() - - # collect all tags (returns NULL on error) - tags_p = Messages._collect_tags(self._msgs) - #reset _msgs as we iterated over it and can do so only once - self._msgs = None - - if tags_p == None: - raise NullPointerError() - return Tags(tags_p, self) - - def __iter__(self): - """ Make Messages an iterator """ - return self - - _valid = nmlib.notmuch_messages_valid - _valid.argtypes = [NotmuchMessagesP] - _valid.restype = bool - - _move_to_next = nmlib.notmuch_messages_move_to_next - _move_to_next.argtypes = [NotmuchMessagesP] - _move_to_next.restype = None - - def __next__(self): - if not self._msgs: - raise NotInitializedError() - - if not self._valid(self._msgs): - self._msgs = None - raise StopIteration - - msg = Message(Messages._get(self._msgs), self) - self._move_to_next(self._msgs) - return msg - next = __next__ # python2.x iterator protocol compatibility - - def __nonzero__(self): - """ - :return: True if there is at least one more thread in the - Iterator, False if not.""" - return self._msgs is not None and \ - self._valid(self._msgs) > 0 - - _destroy = nmlib.notmuch_messages_destroy - _destroy.argtypes = [NotmuchMessagesP] - _destroy.restype = None - - def __del__(self): - """Close and free the notmuch Messages""" - if self._msgs is not None: - self._destroy(self._msgs) - - def format_messages(self, format, indent=0, entire_thread=False): - """Formats messages as needed for 'notmuch show'. - - :param format: A string of either 'text' or 'json'. - :param indent: A number indicating the reply depth of these messages. - :param entire_thread: A bool, indicating whether we want to output - whole threads or only the matching messages. - :return: a list of lines - """ - result = list() - - if format.lower() == "text": - set_start = "" - set_end = "" - set_sep = "" - elif format.lower() == "json": - set_start = "[" - set_end = "]" - set_sep = ", " - else: - raise TypeError("format must be either 'text' or 'json'") - - first_set = True - - result.append(set_start) - - # iterate through all toplevel messages in this thread - for msg in self: - # if not msg: - # break - if not first_set: - result.append(set_sep) - first_set = False - - result.append(set_start) - match = msg.is_match() - next_indent = indent - - if (match or entire_thread): - if format.lower() == "text": - result.append(msg.format_message_as_text(indent)) - else: - result.append(msg.format_message_as_json(indent)) - next_indent = indent + 1 - - # get replies and print them also out (if there are any) - replies = msg.get_replies().format_messages(format, next_indent, entire_thread) - if replies: - result.append(set_sep) - result.extend(replies) - - result.append(set_end) - result.append(set_end) - - return result - - def print_messages(self, format, indent=0, entire_thread=False, handle=sys.stdout): - """Outputs messages as needed for 'notmuch show' to a file like object. - - :param format: A string of either 'text' or 'json'. - :param handle: A file like object to print to (default is sys.stdout). - :param indent: A number indicating the reply depth of these messages. - :param entire_thread: A bool, indicating whether we want to output - whole threads or only the matching messages. - """ - handle.write(''.join(self.format_messages(format, indent, entire_thread))) - - -class EmptyMessagesResult(Messages): - def __init__(self, parent): - self._msgs = None - self._parent = parent - - def __next__(self): - raise StopIteration() - next = __next__ - - class Message(Python3StringMixIn): """Represents a single Email message @@ -418,6 +186,8 @@ class Message(Python3StringMixIn): msgs_p = Message._get_replies(self._msg) + from .messages import Messages, EmptyMessagesResult + if not msgs_p: return EmptyMessagesResult(self) diff --git a/bindings/python/notmuch/messages.py b/bindings/python/notmuch/messages.py new file mode 100644 index 0000000..20d3632 --- /dev/null +++ b/bindings/python/notmuch/messages.py @@ -0,0 +1,262 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth ' + Jesse Rosenthal +""" + +from .globals import ( + nmlib, + NullPointerError, + NotInitializedError, + NotmuchTagsP, + NotmuchMessageP, + NotmuchMessagesP, +) +from .tag import Tags +from .message import Message + +import sys + +class Messages(object): + """Represents a list of notmuch messages + + This object provides an iterator over a list of notmuch messages + (Technically, it provides a wrapper for the underlying + *notmuch_messages_t* structure). Do note that the underlying library + only provides a one-time iterator (it cannot reset the iterator to + the start). Thus iterating over the function will "exhaust" the list + of messages, and a subsequent iteration attempt will raise a + :exc:`NotInitializedError`. If you need to + re-iterate over a list of messages you will need to retrieve a new + :class:`Messages` object or cache your :class:`Message`\s in a list + via:: + + msglist = list(msgs) + + You can store and reuse the single :class:`Message` objects as often + as you want as long as you keep the parent :class:`Messages` object + around. (Due to hierarchical memory allocation, all derived + :class:`Message` objects will be invalid when we delete the parent + :class:`Messages` object, even if it was already exhausted.) So + this works:: + + db = Database() + msgs = Query(db,'').search_messages() #get a Messages() object + msglist = list(msgs) + + # msgs is "exhausted" now and msgs.next() will raise an exception. + # However it will be kept alive until all retrieved Message() + # objects are also deleted. If you do e.g. an explicit del(msgs) + # here, the following lines would fail. + + # You can reiterate over *msglist* however as often as you want. + # It is simply a list with :class:`Message`s. + + print (msglist[0].get_filename()) + print (msglist[1].get_filename()) + print (msglist[0].get_message_id()) + + + As :class:`Message` implements both __hash__() and __cmp__(), it is + possible to make sets out of :class:`Messages` and use set + arithmetic (this happens in python and will of course be *much* + slower than redoing a proper query with the appropriate filters:: + + s1, s2 = set(msgs1), set(msgs2) + s.union(s2) + s1 -= s2 + ... + + Be careful when using set arithmetic between message sets derived + from different Databases (ie the same database reopened after + messages have changed). If messages have added or removed associated + files in the meantime, it is possible that the same message would be + considered as a different object (as it points to a different file). + """ + + #notmuch_messages_get + _get = nmlib.notmuch_messages_get + _get.argtypes = [NotmuchMessagesP] + _get.restype = NotmuchMessageP + + _collect_tags = nmlib.notmuch_messages_collect_tags + _collect_tags.argtypes = [NotmuchMessagesP] + _collect_tags.restype = NotmuchTagsP + + def __init__(self, msgs_p, parent=None): + """ + :param msgs_p: A pointer to an underlying *notmuch_messages_t* + structure. These are not publically exposed, so a user + will almost never instantiate a :class:`Messages` object + herself. They are usually handed back as a result, + e.g. in :meth:`Query.search_messages`. *msgs_p* must be + valid, we will raise an :exc:`NullPointerError` if it is + `None`. + :type msgs_p: :class:`ctypes.c_void_p` + :param parent: The parent object + (ie :class:`Query`) these tags are derived from. It saves + a reference to it, so we can automatically delete the db + object once all derived objects are dead. + :TODO: Make the iterator work more than once and cache the tags in + the Python object.(?) + """ + if not msgs_p: + raise NullPointerError() + + self._msgs = msgs_p + #store parent, so we keep them alive as long as self is alive + self._parent = parent + + def collect_tags(self): + """Return the unique :class:`Tags` in the contained messages + + :returns: :class:`Tags` + :exceptions: :exc:`NotInitializedError` if not init'ed + + .. note:: + + :meth:`collect_tags` will iterate over the messages and therefore + will not allow further iterations. + """ + if not self._msgs: + raise NotInitializedError() + + # collect all tags (returns NULL on error) + tags_p = Messages._collect_tags(self._msgs) + #reset _msgs as we iterated over it and can do so only once + self._msgs = None + + if tags_p == None: + raise NullPointerError() + return Tags(tags_p, self) + + def __iter__(self): + """ Make Messages an iterator """ + return self + + _valid = nmlib.notmuch_messages_valid + _valid.argtypes = [NotmuchMessagesP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_messages_move_to_next + _move_to_next.argtypes = [NotmuchMessagesP] + _move_to_next.restype = None + + def __next__(self): + if not self._msgs: + raise NotInitializedError() + + if not self._valid(self._msgs): + self._msgs = None + raise StopIteration + + msg = Message(Messages._get(self._msgs), self) + self._move_to_next(self._msgs) + return msg + next = __next__ # python2.x iterator protocol compatibility + + def __nonzero__(self): + """ + :return: True if there is at least one more thread in the + Iterator, False if not.""" + return self._msgs is not None and \ + self._valid(self._msgs) > 0 + + _destroy = nmlib.notmuch_messages_destroy + _destroy.argtypes = [NotmuchMessagesP] + _destroy.restype = None + + def __del__(self): + """Close and free the notmuch Messages""" + if self._msgs is not None: + self._destroy(self._msgs) + + def format_messages(self, format, indent=0, entire_thread=False): + """Formats messages as needed for 'notmuch show'. + + :param format: A string of either 'text' or 'json'. + :param indent: A number indicating the reply depth of these messages. + :param entire_thread: A bool, indicating whether we want to output + whole threads or only the matching messages. + :return: a list of lines + """ + result = list() + + if format.lower() == "text": + set_start = "" + set_end = "" + set_sep = "" + elif format.lower() == "json": + set_start = "[" + set_end = "]" + set_sep = ", " + else: + raise TypeError("format must be either 'text' or 'json'") + + first_set = True + + result.append(set_start) + + # iterate through all toplevel messages in this thread + for msg in self: + # if not msg: + # break + if not first_set: + result.append(set_sep) + first_set = False + + result.append(set_start) + match = msg.is_match() + next_indent = indent + + if (match or entire_thread): + if format.lower() == "text": + result.append(msg.format_message_as_text(indent)) + else: + result.append(msg.format_message_as_json(indent)) + next_indent = indent + 1 + + # get replies and print them also out (if there are any) + replies = msg.get_replies().format_messages(format, next_indent, entire_thread) + if replies: + result.append(set_sep) + result.extend(replies) + + result.append(set_end) + result.append(set_end) + + return result + + def print_messages(self, format, indent=0, entire_thread=False, handle=sys.stdout): + """Outputs messages as needed for 'notmuch show' to a file like object. + + :param format: A string of either 'text' or 'json'. + :param handle: A file like object to print to (default is sys.stdout). + :param indent: A number indicating the reply depth of these messages. + :param entire_thread: A bool, indicating whether we want to output + whole threads or only the matching messages. + """ + handle.write(''.join(self.format_messages(format, indent, entire_thread))) + +class EmptyMessagesResult(Messages): + def __init__(self, parent): + self._msgs = None + self._parent = parent + + def __next__(self): + raise StopIteration() + next = __next__ diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index 15ed153..b669a3e 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -30,7 +30,7 @@ from notmuch.globals import ( NotInitializedError, ) from notmuch.thread import Threads -from notmuch.message import Messages +from .messages import Messages class Query(object): diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index d1ba3e5..c599bcb 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -28,7 +28,7 @@ from notmuch.globals import ( NotmuchMessagesP, NotmuchTagsP, ) -from notmuch.message import Messages +from .messages import Messages from notmuch.tag import Tags from datetime import date -- cgit v1.2.3 From 76a2db3d7b92bc1a8be75f673dc384c46cf02fab Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 22:39:52 +0100 Subject: python: move Threads class into its own file Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/__init__.py | 3 +- bindings/python/notmuch/query.py | 2 +- bindings/python/notmuch/thread.py | 154 ------------------------------- bindings/python/notmuch/threads.py | 178 ++++++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 156 deletions(-) create mode 100644 bindings/python/notmuch/threads.py diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index b8f3610..89b9849 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -58,7 +58,8 @@ from .message import Message from .messages import Messages from .query import Query from .tag import Tags -from .thread import Threads, Thread +from .thread import Thread +from .threads import Threads from .globals import ( nmlib, STATUS, diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index b669a3e..fcd67e5 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -29,7 +29,7 @@ from notmuch.globals import ( NullPointerError, NotInitializedError, ) -from notmuch.thread import Threads +from .threads import Threads from .messages import Messages diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index c599bcb..0dac522 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -20,11 +20,9 @@ Copyright 2010 Sebastian Spaeth ' from ctypes import c_char_p, c_long, c_int from notmuch.globals import ( nmlib, - Python3StringMixIn, NullPointerError, NotInitializedError, NotmuchThreadP, - NotmuchThreadsP, NotmuchMessagesP, NotmuchTagsP, ) @@ -32,158 +30,6 @@ from .messages import Messages from notmuch.tag import Tags from datetime import date - -class Threads(Python3StringMixIn): - """Represents a list of notmuch threads - - This object provides an iterator over a list of notmuch threads - (Technically, it provides a wrapper for the underlying - *notmuch_threads_t* structure). Do note that the underlying - library only provides a one-time iterator (it cannot reset the - iterator to the start). Thus iterating over the function will - "exhaust" the list of threads, and a subsequent iteration attempt - will raise a :exc:`NotInitializedError`. Also - note, that any function that uses iteration will also - exhaust the messages. So both:: - - for thread in threads: print thread - - as well as:: - - number_of_msgs = len(threads) - - will "exhaust" the threads. If you need to re-iterate over a list of - messages you will need to retrieve a new :class:`Threads` object. - - Things are not as bad as it seems though, you can store and reuse - the single Thread objects as often as you want as long as you - keep the parent Threads object around. (Recall that due to - hierarchical memory allocation, all derived Threads objects will - be invalid when we delete the parent Threads() object, even if it - was already "exhausted".) So this works:: - - db = Database() - threads = Query(db,'').search_threads() #get a Threads() object - threadlist = [] - for thread in threads: - threadlist.append(thread) - - # threads is "exhausted" now and even len(threads) will raise an - # exception. - # However it will be kept around until all retrieved Thread() objects are - # also deleted. If you did e.g. an explicit del(threads) here, the - # following lines would fail. - - # You can reiterate over *threadlist* however as often as you want. - # It is simply a list with Thread objects. - - print (threadlist[0].get_thread_id()) - print (threadlist[1].get_thread_id()) - print (threadlist[0].get_total_messages()) - """ - - #notmuch_threads_get - _get = nmlib.notmuch_threads_get - _get.argtypes = [NotmuchThreadsP] - _get.restype = NotmuchThreadP - - def __init__(self, threads_p, parent=None): - """ - :param threads_p: A pointer to an underlying *notmuch_threads_t* - structure. These are not publically exposed, so a user - will almost never instantiate a :class:`Threads` object - herself. They are usually handed back as a result, - e.g. in :meth:`Query.search_threads`. *threads_p* must be - valid, we will raise an :exc:`NullPointerError` if it is - `None`. - :type threads_p: :class:`ctypes.c_void_p` - :param parent: The parent object - (ie :class:`Query`) these tags are derived from. It saves - a reference to it, so we can automatically delete the db - object once all derived objects are dead. - :TODO: Make the iterator work more than once and cache the tags in - the Python object.(?) - """ - if not threads_p: - raise NullPointerError() - - self._threads = threads_p - #store parent, so we keep them alive as long as self is alive - self._parent = parent - - def __iter__(self): - """ Make Threads an iterator """ - return self - - _valid = nmlib.notmuch_threads_valid - _valid.argtypes = [NotmuchThreadsP] - _valid.restype = bool - - _move_to_next = nmlib.notmuch_threads_move_to_next - _move_to_next.argtypes = [NotmuchThreadsP] - _move_to_next.restype = None - - def __next__(self): - if not self._threads: - raise NotInitializedError() - - if not self._valid(self._threads): - self._threads = None - raise StopIteration - - thread = Thread(Threads._get(self._threads), self) - self._move_to_next(self._threads) - return thread - next = __next__ # python2.x iterator protocol compatibility - - def __len__(self): - """len(:class:`Threads`) returns the number of contained Threads - - .. note:: As this iterates over the threads, we will not be able to - iterate over them again! So this will fail:: - - #THIS FAILS - threads = Database().create_query('').search_threads() - if len(threads) > 0: #this 'exhausts' threads - # next line raises :exc:`NotInitializedError`!!! - for thread in threads: print thread - """ - if not self._threads: - raise NotInitializedError() - - i = 0 - # returns 'bool'. On out-of-memory it returns None - while self._valid(self._threads): - self._move_to_next(self._threads) - i += 1 - # reset self._threads to mark as "exhausted" - self._threads = None - return i - - def __nonzero__(self): - """Check if :class:`Threads` contains at least one more valid thread - - The existence of this function makes 'if Threads: foo' work, as - that will implicitely call len() exhausting the iterator if - __nonzero__ does not exist. This function makes `bool(Threads())` - work repeatedly. - - :return: True if there is at least one more thread in the - Iterator, False if not. None on a "Out-of-memory" error. - """ - return self._threads is not None and \ - self._valid(self._threads) > 0 - - _destroy = nmlib.notmuch_threads_destroy - _destroy.argtypes = [NotmuchThreadsP] - _destroy.argtypes = None - - def __del__(self): - """Close and free the notmuch Threads""" - if self._threads is not None: - self._destroy(self._threads) - - class Thread(object): """Represents a single message thread.""" diff --git a/bindings/python/notmuch/threads.py b/bindings/python/notmuch/threads.py new file mode 100644 index 0000000..9d305e2 --- /dev/null +++ b/bindings/python/notmuch/threads.py @@ -0,0 +1,178 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth ' +""" + +from notmuch.globals import ( + nmlib, + Python3StringMixIn, + NullPointerError, + NotInitializedError, + NotmuchThreadP, + NotmuchThreadsP, +) +from .thread import Thread + +class Threads(Python3StringMixIn): + """Represents a list of notmuch threads + + This object provides an iterator over a list of notmuch threads + (Technically, it provides a wrapper for the underlying + *notmuch_threads_t* structure). Do note that the underlying + library only provides a one-time iterator (it cannot reset the + iterator to the start). Thus iterating over the function will + "exhaust" the list of threads, and a subsequent iteration attempt + will raise a :exc:`NotInitializedError`. Also + note, that any function that uses iteration will also + exhaust the messages. So both:: + + for thread in threads: print thread + + as well as:: + + number_of_msgs = len(threads) + + will "exhaust" the threads. If you need to re-iterate over a list of + messages you will need to retrieve a new :class:`Threads` object. + + Things are not as bad as it seems though, you can store and reuse + the single Thread objects as often as you want as long as you + keep the parent Threads object around. (Recall that due to + hierarchical memory allocation, all derived Threads objects will + be invalid when we delete the parent Threads() object, even if it + was already "exhausted".) So this works:: + + db = Database() + threads = Query(db,'').search_threads() #get a Threads() object + threadlist = [] + for thread in threads: + threadlist.append(thread) + + # threads is "exhausted" now and even len(threads) will raise an + # exception. + # However it will be kept around until all retrieved Thread() objects are + # also deleted. If you did e.g. an explicit del(threads) here, the + # following lines would fail. + + # You can reiterate over *threadlist* however as often as you want. + # It is simply a list with Thread objects. + + print (threadlist[0].get_thread_id()) + print (threadlist[1].get_thread_id()) + print (threadlist[0].get_total_messages()) + """ + + #notmuch_threads_get + _get = nmlib.notmuch_threads_get + _get.argtypes = [NotmuchThreadsP] + _get.restype = NotmuchThreadP + + def __init__(self, threads_p, parent=None): + """ + :param threads_p: A pointer to an underlying *notmuch_threads_t* + structure. These are not publically exposed, so a user + will almost never instantiate a :class:`Threads` object + herself. They are usually handed back as a result, + e.g. in :meth:`Query.search_threads`. *threads_p* must be + valid, we will raise an :exc:`NullPointerError` if it is + `None`. + :type threads_p: :class:`ctypes.c_void_p` + :param parent: The parent object + (ie :class:`Query`) these tags are derived from. It saves + a reference to it, so we can automatically delete the db + object once all derived objects are dead. + :TODO: Make the iterator work more than once and cache the tags in + the Python object.(?) + """ + if not threads_p: + raise NullPointerError() + + self._threads = threads_p + #store parent, so we keep them alive as long as self is alive + self._parent = parent + + def __iter__(self): + """ Make Threads an iterator """ + return self + + _valid = nmlib.notmuch_threads_valid + _valid.argtypes = [NotmuchThreadsP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_threads_move_to_next + _move_to_next.argtypes = [NotmuchThreadsP] + _move_to_next.restype = None + + def __next__(self): + if not self._threads: + raise NotInitializedError() + + if not self._valid(self._threads): + self._threads = None + raise StopIteration + + thread = Thread(Threads._get(self._threads), self) + self._move_to_next(self._threads) + return thread + next = __next__ # python2.x iterator protocol compatibility + + def __len__(self): + """len(:class:`Threads`) returns the number of contained Threads + + .. note:: As this iterates over the threads, we will not be able to + iterate over them again! So this will fail:: + + #THIS FAILS + threads = Database().create_query('').search_threads() + if len(threads) > 0: #this 'exhausts' threads + # next line raises :exc:`NotInitializedError`!!! + for thread in threads: print thread + """ + if not self._threads: + raise NotInitializedError() + + i = 0 + # returns 'bool'. On out-of-memory it returns None + while self._valid(self._threads): + self._move_to_next(self._threads) + i += 1 + # reset self._threads to mark as "exhausted" + self._threads = None + return i + + def __nonzero__(self): + """Check if :class:`Threads` contains at least one more valid thread + + The existence of this function makes 'if Threads: foo' work, as + that will implicitely call len() exhausting the iterator if + __nonzero__ does not exist. This function makes `bool(Threads())` + work repeatedly. + + :return: True if there is at least one more thread in the + Iterator, False if not. None on a "Out-of-memory" error. + """ + return self._threads is not None and \ + self._valid(self._threads) > 0 + + _destroy = nmlib.notmuch_threads_destroy + _destroy.argtypes = [NotmuchThreadsP] + _destroy.argtypes = None + + def __del__(self): + """Close and free the notmuch Threads""" + if self._threads is not None: + self._destroy(self._threads) -- cgit v1.2.3 From df0e1cf7884f93bbbf70786d0bffc45824ae01c1 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 22:44:35 +0100 Subject: python: rename filename.py into filenames.py Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/__init__.py | 2 +- bindings/python/notmuch/directory.py | 2 +- bindings/python/notmuch/filename.py | 167 ----------------------------------- bindings/python/notmuch/filenames.py | 167 +++++++++++++++++++++++++++++++++++ bindings/python/notmuch/message.py | 2 +- 5 files changed, 170 insertions(+), 170 deletions(-) delete mode 100644 bindings/python/notmuch/filename.py create mode 100644 bindings/python/notmuch/filenames.py diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index 89b9849..fddc492 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -53,7 +53,7 @@ Copyright 2010-2011 Sebastian Spaeth """ from .database import Database from .directory import Directory -from .filename import Filenames +from .filenames import Filenames from .message import Message from .messages import Messages from .query import Query diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py index 3e0763f..b679aa2 100644 --- a/bindings/python/notmuch/directory.py +++ b/bindings/python/notmuch/directory.py @@ -26,7 +26,7 @@ from notmuch.globals import ( NotmuchDirectoryP, NotmuchFilenamesP ) -from .filename import Filenames +from .filenames import Filenames class Directory(object): """Represents a directory entry in the notmuch directory diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py deleted file mode 100644 index 232a9ed..0000000 --- a/bindings/python/notmuch/filename.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -This file is part of notmuch. - -Notmuch is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the -Free Software Foundation, either version 3 of the License, or (at your -option) any later version. - -Notmuch is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -for more details. - -You should have received a copy of the GNU General Public License -along with notmuch. If not, see . - -Copyright 2010 Sebastian Spaeth ' -""" -from ctypes import c_char_p -from notmuch.globals import ( - nmlib, - NullPointerError, - NotInitializedError, - NotmuchMessageP, - NotmuchFilenamesP, - Python3StringMixIn, -) - - -class Filenames(Python3StringMixIn): - """Represents a list of filenames as returned by notmuch - - This object contains the Filenames iterator. The main function is - as_generator() which will return a generator so we can do a Filenamesth an - iterator over a list of notmuch filenames. Do note that the underlying - library only provides a one-time iterator (it cannot reset the iterator to - the start). Thus iterating over the function will "exhaust" the list of - tags, and a subsequent iteration attempt will raise a - :exc:`NotInitializedError`. Also note, that any function that uses - iteration (nearly all) will also exhaust the tags. So both:: - - for name in filenames: print name - - as well as:: - - number_of_names = len(names) - - and even a simple:: - - #str() iterates over all tags to construct a space separated list - print(str(filenames)) - - will "exhaust" the Filenames. However, you can use - :meth:`Message.get_filenames` repeatedly to get fresh Filenames - objects to perform various actions on filenames. - """ - - #notmuch_filenames_get - _get = nmlib.notmuch_filenames_get - _get.argtypes = [NotmuchFilenamesP] - _get.restype = c_char_p - - def __init__(self, files_p, parent): - """ - :param files_p: A pointer to an underlying *notmuch_tags_t* - structure. These are not publically exposed, so a user - will almost never instantiate a :class:`Tags` object - herself. They are usually handed back as a result, - e.g. in :meth:`Database.get_all_tags`. *tags_p* must be - valid, we will raise an :exc:`NullPointerError` - if it is `None`. - :type files_p: :class:`ctypes.c_void_p` - :param parent: The parent object (ie :class:`Message` these - filenames are derived from, and saves a - reference to it, so we can automatically delete the db object - once all derived objects are dead. - """ - if not files_p: - raise NullPointerError() - - self._files_p = files_p - #save reference to parent object so we keep it alive - self._parent = parent - - def __iter__(self): - """ Make Filenames an iterator """ - return self - - _valid = nmlib.notmuch_filenames_valid - _valid.argtypes = [NotmuchFilenamesP] - _valid.restype = bool - - _move_to_next = nmlib.notmuch_filenames_move_to_next - _move_to_next.argtypes = [NotmuchFilenamesP] - _move_to_next.restype = None - - def __next__(self): - if not self._files_p: - raise NotInitializedError() - - if not self._valid(self._files_p): - self._files_p = None - raise StopIteration - - file_ = Filenames._get(self._files_p) - self._move_to_next(self._files_p) - return file_.decode('utf-8', 'ignore') - next = __next__ # python2.x iterator protocol compatibility - - def as_generator(self): - """Return generator of Filenames - - This is the main function that will usually be used by the - user. - - .. deprecated:: 0.12 - :class:`Filenames` objects implement the - iterator protocol. - """ - return self - - def __unicode__(self): - """Represent Filenames() as newline-separated list of full paths - - .. note:: As this iterates over the filenames, we will not be - able to iterate over them again (as in retrieve them)! If - the tags have been exhausted already, this will raise a - :exc:`NotInitializedError` on subsequent - attempts. However, you can use - :meth:`Message.get_filenames` repeatedly to perform - various actions on filenames. - """ - return "\n".join(self) - - _destroy = nmlib.notmuch_filenames_destroy - _destroy.argtypes = [NotmuchMessageP] - _destroy.restype = None - - def __del__(self): - """Close and free the notmuch filenames""" - if self._files_p is not None: - self._destroy(self._files_p) - - def __len__(self): - """len(:class:`Filenames`) returns the number of contained files - - .. note:: - - As this iterates over the files, we will not be able to - iterate over them again! So this will fail:: - - #THIS FAILS - files = Database().get_directory('').get_child_files() - if len(files) > 0: # this 'exhausts' msgs - # next line raises - # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) - for file in files: print file - """ - if not self._files_p: - raise NotInitializedError() - - i = 0 - while self._valid(self._files_p): - self._move_to_next(self._files_p) - i += 1 - self._files_p = None - return i diff --git a/bindings/python/notmuch/filenames.py b/bindings/python/notmuch/filenames.py new file mode 100644 index 0000000..232a9ed --- /dev/null +++ b/bindings/python/notmuch/filenames.py @@ -0,0 +1,167 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth ' +""" +from ctypes import c_char_p +from notmuch.globals import ( + nmlib, + NullPointerError, + NotInitializedError, + NotmuchMessageP, + NotmuchFilenamesP, + Python3StringMixIn, +) + + +class Filenames(Python3StringMixIn): + """Represents a list of filenames as returned by notmuch + + This object contains the Filenames iterator. The main function is + as_generator() which will return a generator so we can do a Filenamesth an + iterator over a list of notmuch filenames. Do note that the underlying + library only provides a one-time iterator (it cannot reset the iterator to + the start). Thus iterating over the function will "exhaust" the list of + tags, and a subsequent iteration attempt will raise a + :exc:`NotInitializedError`. Also note, that any function that uses + iteration (nearly all) will also exhaust the tags. So both:: + + for name in filenames: print name + + as well as:: + + number_of_names = len(names) + + and even a simple:: + + #str() iterates over all tags to construct a space separated list + print(str(filenames)) + + will "exhaust" the Filenames. However, you can use + :meth:`Message.get_filenames` repeatedly to get fresh Filenames + objects to perform various actions on filenames. + """ + + #notmuch_filenames_get + _get = nmlib.notmuch_filenames_get + _get.argtypes = [NotmuchFilenamesP] + _get.restype = c_char_p + + def __init__(self, files_p, parent): + """ + :param files_p: A pointer to an underlying *notmuch_tags_t* + structure. These are not publically exposed, so a user + will almost never instantiate a :class:`Tags` object + herself. They are usually handed back as a result, + e.g. in :meth:`Database.get_all_tags`. *tags_p* must be + valid, we will raise an :exc:`NullPointerError` + if it is `None`. + :type files_p: :class:`ctypes.c_void_p` + :param parent: The parent object (ie :class:`Message` these + filenames are derived from, and saves a + reference to it, so we can automatically delete the db object + once all derived objects are dead. + """ + if not files_p: + raise NullPointerError() + + self._files_p = files_p + #save reference to parent object so we keep it alive + self._parent = parent + + def __iter__(self): + """ Make Filenames an iterator """ + return self + + _valid = nmlib.notmuch_filenames_valid + _valid.argtypes = [NotmuchFilenamesP] + _valid.restype = bool + + _move_to_next = nmlib.notmuch_filenames_move_to_next + _move_to_next.argtypes = [NotmuchFilenamesP] + _move_to_next.restype = None + + def __next__(self): + if not self._files_p: + raise NotInitializedError() + + if not self._valid(self._files_p): + self._files_p = None + raise StopIteration + + file_ = Filenames._get(self._files_p) + self._move_to_next(self._files_p) + return file_.decode('utf-8', 'ignore') + next = __next__ # python2.x iterator protocol compatibility + + def as_generator(self): + """Return generator of Filenames + + This is the main function that will usually be used by the + user. + + .. deprecated:: 0.12 + :class:`Filenames` objects implement the + iterator protocol. + """ + return self + + def __unicode__(self): + """Represent Filenames() as newline-separated list of full paths + + .. note:: As this iterates over the filenames, we will not be + able to iterate over them again (as in retrieve them)! If + the tags have been exhausted already, this will raise a + :exc:`NotInitializedError` on subsequent + attempts. However, you can use + :meth:`Message.get_filenames` repeatedly to perform + various actions on filenames. + """ + return "\n".join(self) + + _destroy = nmlib.notmuch_filenames_destroy + _destroy.argtypes = [NotmuchMessageP] + _destroy.restype = None + + def __del__(self): + """Close and free the notmuch filenames""" + if self._files_p is not None: + self._destroy(self._files_p) + + def __len__(self): + """len(:class:`Filenames`) returns the number of contained files + + .. note:: + + As this iterates over the files, we will not be able to + iterate over them again! So this will fail:: + + #THIS FAILS + files = Database().get_directory('').get_child_files() + if len(files) > 0: # this 'exhausts' msgs + # next line raises + # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) + for file in files: print file + """ + if not self._files_p: + raise NotInitializedError() + + i = 0 + while self._valid(self._files_p): + self._move_to_next(self._files_p) + i += 1 + self._files_p = None + return i diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 20ba9cb..d17b9bc 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -36,7 +36,7 @@ from .globals import ( NotmuchFilenamesP, ) from .tag import Tags -from .filename import Filenames +from .filenames import Filenames import email try: -- cgit v1.2.3 From a7561cc20b17669784c3259afcbcef98029f93e9 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Thu, 23 Feb 2012 00:11:22 +0100 Subject: python: move the exception classes into error.py Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/__init__.py | 4 +- bindings/python/notmuch/database.py | 14 +-- bindings/python/notmuch/directory.py | 6 +- bindings/python/notmuch/errors.py | 183 +++++++++++++++++++++++++++++++++++ bindings/python/notmuch/filenames.py | 6 +- bindings/python/notmuch/globals.py | 160 +----------------------------- bindings/python/notmuch/message.py | 10 +- bindings/python/notmuch/messages.py | 6 +- bindings/python/notmuch/query.py | 2 + bindings/python/notmuch/tag.py | 4 +- bindings/python/notmuch/thread.py | 6 +- bindings/python/notmuch/threads.py | 6 +- 12 files changed, 225 insertions(+), 182 deletions(-) create mode 100644 bindings/python/notmuch/errors.py diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py index fddc492..5561624 100644 --- a/bindings/python/notmuch/__init__.py +++ b/bindings/python/notmuch/__init__.py @@ -60,8 +60,8 @@ from .query import Query from .tag import Tags from .thread import Thread from .threads import Threads -from .globals import ( - nmlib, +from .globals import nmlib +from .errors import ( STATUS, NotmuchError, OutOfMemoryError, diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 800264b..44d40fd 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -22,12 +22,6 @@ import codecs from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER from notmuch.globals import ( nmlib, - STATUS, - FileError, - NotmuchError, - NullPointerError, - NotInitializedError, - ReadOnlyDatabaseError, Enum, _str, NotmuchDatabaseP, @@ -35,6 +29,14 @@ from notmuch.globals import ( NotmuchMessageP, NotmuchTagsP, ) +from .errors import ( + STATUS, + FileError, + NotmuchError, + NullPointerError, + NotInitializedError, + ReadOnlyDatabaseError, +) from notmuch.message import Message from notmuch.tag import Tags from .query import Query diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py index b679aa2..0c5e015 100644 --- a/bindings/python/notmuch/directory.py +++ b/bindings/python/notmuch/directory.py @@ -20,11 +20,13 @@ Copyright 2010 Sebastian Spaeth ' from ctypes import c_uint, c_long from notmuch.globals import ( nmlib, + NotmuchDirectoryP, + NotmuchFilenamesP +) +from .errors import ( STATUS, NotmuchError, NotInitializedError, - NotmuchDirectoryP, - NotmuchFilenamesP ) from .filenames import Filenames diff --git a/bindings/python/notmuch/errors.py b/bindings/python/notmuch/errors.py new file mode 100644 index 0000000..f153a9c --- /dev/null +++ b/bindings/python/notmuch/errors.py @@ -0,0 +1,183 @@ +""" +This file is part of notmuch. + +Notmuch is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Notmuch is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see . + +Copyright 2010 Sebastian Spaeth +""" + +from ctypes import c_char_p, c_int + +from .globals import ( + nmlib, + Enum, + Python3StringMixIn, +) + +class Status(Enum): + """Enum with a string representation of a notmuch_status_t value.""" + _status2str = nmlib.notmuch_status_to_string + _status2str.restype = c_char_p + _status2str.argtypes = [c_int] + + def __init__(self, statuslist): + """It is initialized with a list of strings that are available as + Status().string1 - Status().stringn attributes. + """ + super(Status, self).__init__(statuslist) + + @classmethod + def status2str(self, status): + """Get a (unicode) string representation of a notmuch_status_t value.""" + # define strings for custom error messages + if status == STATUS.NOT_INITIALIZED: + return "Operation on uninitialized object impossible." + return unicode(Status._status2str(status)) + +STATUS = Status(['SUCCESS', + 'OUT_OF_MEMORY', + 'READ_ONLY_DATABASE', + 'XAPIAN_EXCEPTION', + 'FILE_ERROR', + 'FILE_NOT_EMAIL', + 'DUPLICATE_MESSAGE_ID', + 'NULL_POINTER', + 'TAG_TOO_LONG', + 'UNBALANCED_FREEZE_THAW', + 'UNBALANCED_ATOMIC', + 'NOT_INITIALIZED']) +"""STATUS is a class, whose attributes provide constants that serve as return +indicators for notmuch functions. Currently the following ones are defined. For +possible return values and specific meaning for each method, see the method +description. + + * SUCCESS + * OUT_OF_MEMORY + * READ_ONLY_DATABASE + * XAPIAN_EXCEPTION + * FILE_ERROR + * FILE_NOT_EMAIL + * DUPLICATE_MESSAGE_ID + * NULL_POINTER + * TAG_TOO_LONG + * UNBALANCED_FREEZE_THAW + * UNBALANCED_ATOMIC + * NOT_INITIALIZED + +Invoke the class method `notmuch.STATUS.status2str` with a status value as +argument to receive a human readable string""" +STATUS.__name__ = 'STATUS' + + +class NotmuchError(Exception, Python3StringMixIn): + """Is initiated with a (notmuch.STATUS[, message=None]). It will not + return an instance of the class NotmuchError, but a derived instance + of a more specific Error Message, e.g. OutOfMemoryError. Each status + but SUCCESS has a corresponding subclassed Exception.""" + + @classmethod + def get_exc_subclass(cls, status): + """Returns a fine grained Exception() type, + detailing the error status""" + subclasses = { + STATUS.OUT_OF_MEMORY: OutOfMemoryError, + STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError, + STATUS.XAPIAN_EXCEPTION: XapianError, + STATUS.FILE_ERROR: FileError, + STATUS.FILE_NOT_EMAIL: FileNotEmailError, + STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError, + STATUS.NULL_POINTER: NullPointerError, + STATUS.TAG_TOO_LONG: TagTooLongError, + STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError, + STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError, + STATUS.NOT_INITIALIZED: NotInitializedError, + } + assert 0 < status <= len(subclasses) + return subclasses[status] + + def __new__(cls, *args, **kwargs): + """Return a correct subclass of NotmuchError if needed + + We return a NotmuchError instance if status is None (or 0) and a + subclass that inherits from NotmuchError depending on the + 'status' parameter otherwise.""" + # get 'status'. Passed in as arg or kwarg? + status = args[0] if len(args) else kwargs.get('status', None) + # no 'status' or cls is subclass already, return 'cls' instance + if not status or cls != NotmuchError: + return super(NotmuchError, cls).__new__(cls) + subclass = cls.get_exc_subclass(status) # which class to use? + return subclass.__new__(subclass, *args, **kwargs) + + def __init__(self, status=None, message=None): + self.status = status + self.message = message + + def __unicode__(self): + if self.message is not None: + return self.message + elif self.status is not None: + return STATUS.status2str(self.status) + else: + return 'Unknown error' + + +# List of Subclassed exceptions that correspond to STATUS values and are +# subclasses of NotmuchError. +class OutOfMemoryError(NotmuchError): + status = STATUS.OUT_OF_MEMORY + + +class ReadOnlyDatabaseError(NotmuchError): + status = STATUS.READ_ONLY_DATABASE + + +class XapianError(NotmuchError): + status = STATUS.XAPIAN_EXCEPTION + + +class FileError(NotmuchError): + status = STATUS.FILE_ERROR + + +class FileNotEmailError(NotmuchError): + status = STATUS.FILE_NOT_EMAIL + + +class DuplicateMessageIdError(NotmuchError): + status = STATUS.DUPLICATE_MESSAGE_ID + + +class NullPointerError(NotmuchError): + status = STATUS.NULL_POINTER + + +class TagTooLongError(NotmuchError): + status = STATUS.TAG_TOO_LONG + + +class UnbalancedFreezeThawError(NotmuchError): + status = STATUS.UNBALANCED_FREEZE_THAW + + +class UnbalancedAtomicError(NotmuchError): + status = STATUS.UNBALANCED_ATOMIC + + +class NotInitializedError(NotmuchError): + """Derived from NotmuchError, this occurs if the underlying data + structure (e.g. database is not initialized (yet) or an iterator has + been exhausted. You can test for NotmuchError with .status = + STATUS.NOT_INITIALIZED""" + status = STATUS.NOT_INITIALIZED diff --git a/bindings/python/notmuch/filenames.py b/bindings/python/notmuch/filenames.py index 232a9ed..12050df 100644 --- a/bindings/python/notmuch/filenames.py +++ b/bindings/python/notmuch/filenames.py @@ -19,12 +19,14 @@ Copyright 2010 Sebastian Spaeth ' from ctypes import c_char_p from notmuch.globals import ( nmlib, - NullPointerError, - NotInitializedError, NotmuchMessageP, NotmuchFilenamesP, Python3StringMixIn, ) +from .errors import ( + NullPointerError, + NotInitializedError, +) class Filenames(Python3StringMixIn): diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py index 4138460..442f3e3 100644 --- a/bindings/python/notmuch/globals.py +++ b/bindings/python/notmuch/globals.py @@ -17,7 +17,7 @@ along with notmuch. If not, see . Copyright 2010 Sebastian Spaeth ' """ import sys -from ctypes import CDLL, c_char_p, c_int, Structure, POINTER +from ctypes import CDLL, Structure, POINTER #----------------------------------------------------------------------------- #package-global instance of the notmuch library @@ -66,164 +66,6 @@ class Enum(object): setattr(self, name, number) -class Status(Enum): - """Enum with a string representation of a notmuch_status_t value.""" - _status2str = nmlib.notmuch_status_to_string - _status2str.restype = c_char_p - _status2str.argtypes = [c_int] - - def __init__(self, statuslist): - """It is initialized with a list of strings that are available as - Status().string1 - Status().stringn attributes. - """ - super(Status, self).__init__(statuslist) - - @classmethod - def status2str(self, status): - """Get a (unicode) string representation of a notmuch_status_t value.""" - # define strings for custom error messages - if status == STATUS.NOT_INITIALIZED: - return "Operation on uninitialized object impossible." - return unicode(Status._status2str(status)) - -STATUS = Status(['SUCCESS', - 'OUT_OF_MEMORY', - 'READ_ONLY_DATABASE', - 'XAPIAN_EXCEPTION', - 'FILE_ERROR', - 'FILE_NOT_EMAIL', - 'DUPLICATE_MESSAGE_ID', - 'NULL_POINTER', - 'TAG_TOO_LONG', - 'UNBALANCED_FREEZE_THAW', - 'UNBALANCED_ATOMIC', - 'NOT_INITIALIZED']) -"""STATUS is a class, whose attributes provide constants that serve as return -indicators for notmuch functions. Currently the following ones are defined. For -possible return values and specific meaning for each method, see the method -description. - - * SUCCESS - * OUT_OF_MEMORY - * READ_ONLY_DATABASE - * XAPIAN_EXCEPTION - * FILE_ERROR - * FILE_NOT_EMAIL - * DUPLICATE_MESSAGE_ID - * NULL_POINTER - * TAG_TOO_LONG - * UNBALANCED_FREEZE_THAW - * UNBALANCED_ATOMIC - * NOT_INITIALIZED - -Invoke the class method `notmuch.STATUS.status2str` with a status value as -argument to receive a human readable string""" -STATUS.__name__ = 'STATUS' - - -class NotmuchError(Exception, Python3StringMixIn): - """Is initiated with a (notmuch.STATUS[, message=None]). It will not - return an instance of the class NotmuchError, but a derived instance - of a more specific Error Message, e.g. OutOfMemoryError. Each status - but SUCCESS has a corresponding subclassed Exception.""" - - @classmethod - def get_exc_subclass(cls, status): - """Returns a fine grained Exception() type, - detailing the error status""" - subclasses = { - STATUS.OUT_OF_MEMORY: OutOfMemoryError, - STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError, - STATUS.XAPIAN_EXCEPTION: XapianError, - STATUS.FILE_ERROR: FileError, - STATUS.FILE_NOT_EMAIL: FileNotEmailError, - STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError, - STATUS.NULL_POINTER: NullPointerError, - STATUS.TAG_TOO_LONG: TagTooLongError, - STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError, - STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError, - STATUS.NOT_INITIALIZED: NotInitializedError, - } - assert 0 < status <= len(subclasses) - return subclasses[status] - - def __new__(cls, *args, **kwargs): - """Return a correct subclass of NotmuchError if needed - - We return a NotmuchError instance if status is None (or 0) and a - subclass that inherits from NotmuchError depending on the - 'status' parameter otherwise.""" - # get 'status'. Passed in as arg or kwarg? - status = args[0] if len(args) else kwargs.get('status', None) - # no 'status' or cls is subclass already, return 'cls' instance - if not status or cls != NotmuchError: - return super(NotmuchError, cls).__new__(cls) - subclass = cls.get_exc_subclass(status) # which class to use? - return subclass.__new__(subclass, *args, **kwargs) - - def __init__(self, status=None, message=None): - self.status = status - self.message = message - - def __unicode__(self): - if self.message is not None: - return self.message - elif self.status is not None: - return STATUS.status2str(self.status) - else: - return 'Unknown error' - - -# List of Subclassed exceptions that correspond to STATUS values and are -# subclasses of NotmuchError. -class OutOfMemoryError(NotmuchError): - status = STATUS.OUT_OF_MEMORY - - -class ReadOnlyDatabaseError(NotmuchError): - status = STATUS.READ_ONLY_DATABASE - - -class XapianError(NotmuchError): - status = STATUS.XAPIAN_EXCEPTION - - -class FileError(NotmuchError): - status = STATUS.FILE_ERROR - - -class FileNotEmailError(NotmuchError): - status = STATUS.FILE_NOT_EMAIL - - -class DuplicateMessageIdError(NotmuchError): - status = STATUS.DUPLICATE_MESSAGE_ID - - -class NullPointerError(NotmuchError): - status = STATUS.NULL_POINTER - - -class TagTooLongError(NotmuchError): - status = STATUS.TAG_TOO_LONG - - -class UnbalancedFreezeThawError(NotmuchError): - status = STATUS.UNBALANCED_FREEZE_THAW - - -class UnbalancedAtomicError(NotmuchError): - status = STATUS.UNBALANCED_ATOMIC - - -class NotInitializedError(NotmuchError): - """Derived from NotmuchError, this occurs if the underlying data - structure (e.g. database is not initialized (yet) or an iterator has - been exhausted. You can test for NotmuchError with .status = - STATUS.NOT_INITIALIZED""" - status = STATUS.NOT_INITIALIZED - - class NotmuchDatabaseS(Structure): pass NotmuchDatabaseP = POINTER(NotmuchDatabaseS) diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index d17b9bc..9eb4fee 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -26,15 +26,17 @@ from .globals import ( Enum, _str, Python3StringMixIn, - STATUS, - NotmuchError, - NullPointerError, - NotInitializedError, NotmuchTagsP, NotmuchMessageP, NotmuchMessagesP, NotmuchFilenamesP, ) +from .errors import ( + STATUS, + NotmuchError, + NullPointerError, + NotInitializedError, +) from .tag import Tags from .filenames import Filenames diff --git a/bindings/python/notmuch/messages.py b/bindings/python/notmuch/messages.py index 20d3632..d94f91b 100644 --- a/bindings/python/notmuch/messages.py +++ b/bindings/python/notmuch/messages.py @@ -20,12 +20,14 @@ Copyright 2010 Sebastian Spaeth ' from .globals import ( nmlib, - NullPointerError, - NotInitializedError, NotmuchTagsP, NotmuchMessageP, NotmuchMessagesP, ) +from .errors import ( + NullPointerError, + NotInitializedError, +) from .tag import Tags from .message import Message diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py index fcd67e5..ddaf8e0 100644 --- a/bindings/python/notmuch/query.py +++ b/bindings/python/notmuch/query.py @@ -26,6 +26,8 @@ from notmuch.globals import ( NotmuchThreadsP, NotmuchDatabaseP, NotmuchMessagesP, +) +from .errors import ( NullPointerError, NotInitializedError, ) diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index 526e51c..711bf53 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -20,9 +20,11 @@ from ctypes import c_char_p from notmuch.globals import ( nmlib, Python3StringMixIn, + NotmuchTagsP, +) +from .errors import ( NullPointerError, NotInitializedError, - NotmuchTagsP, ) diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index 0dac522..a759c90 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -20,12 +20,14 @@ Copyright 2010 Sebastian Spaeth ' from ctypes import c_char_p, c_long, c_int from notmuch.globals import ( nmlib, - NullPointerError, - NotInitializedError, NotmuchThreadP, NotmuchMessagesP, NotmuchTagsP, ) +from .errors import ( + NullPointerError, + NotInitializedError, +) from .messages import Messages from notmuch.tag import Tags from datetime import date diff --git a/bindings/python/notmuch/threads.py b/bindings/python/notmuch/threads.py index 9d305e2..690206e 100644 --- a/bindings/python/notmuch/threads.py +++ b/bindings/python/notmuch/threads.py @@ -20,11 +20,13 @@ Copyright 2010 Sebastian Spaeth ' from notmuch.globals import ( nmlib, Python3StringMixIn, - NullPointerError, - NotInitializedError, NotmuchThreadP, NotmuchThreadsP, ) +from .errors import ( + NullPointerError, + NotInitializedError, +) from .thread import Thread class Threads(Python3StringMixIn): -- cgit v1.2.3 From 90fb4e8334e043201dfb30f47c072ab9ebb662e1 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Thu, 23 Feb 2012 00:14:59 +0100 Subject: python: mention the exception class refactoring in the docs Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/status_and_errors.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bindings/python/docs/source/status_and_errors.rst b/bindings/python/docs/source/status_and_errors.rst index bc0d0d2..dd6e31f 100644 --- a/bindings/python/docs/source/status_and_errors.rst +++ b/bindings/python/docs/source/status_and_errors.rst @@ -5,6 +5,12 @@ Status and Errors Some methods return a status, indicating if an operation was successful and what the error was. Most of these status codes are expressed as a specific value, the :class:`notmuch.STATUS`. +.. note:: + + Prior to version 0.12 the exception classes and the enumeration + :class:`notmuch.STATUS` were defined in `notmuch.globals`. They + have since then been moved into `notmuch.errors`. + :class:`STATUS` -- Notmuch operation return value -------------------------------------------------- -- cgit v1.2.3 From fb52083bf7eff44f27b2f2fffcbfbcb4faaeda3c Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 24 Feb 2012 00:38:47 +0100 Subject: python: strip module prefix in the sphinx documentation Remove the notmuch prefix from classes in the documentation. This change makes the table of contents look much nicer. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index e87a865..fc3179b 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -42,10 +42,10 @@ More information on specific topics can be found on the following pages: .. automodule:: notmuch -:class:`notmuch.Database` -- The underlying notmuch database +:class:`Database` -- The underlying notmuch database --------------------------------------------------------------------- -.. autoclass:: notmuch.Database([path=None[, create=False[, mode=MODE.READ_ONLY]]]) +.. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]]) .. automethod:: create @@ -90,10 +90,10 @@ More information on specific topics can be found on the following pages: .. autoattribute:: db_p -:class:`notmuch.Query` -- A search query +:class:`Query` -- A search query ------------------------------------------------- -.. autoclass:: notmuch.Query +.. autoclass:: Query .. automethod:: create @@ -216,10 +216,10 @@ More information on specific topics can be found on the following pages: .. automethod:: __str__ -:class:`notmuch.Threads` -- Threads iterator +:class:`Threads` -- Threads iterator ----------------------------------------------------- -.. autoclass:: notmuch.Threads +.. autoclass:: Threads .. automethod:: __len__ @@ -254,28 +254,28 @@ More information on specific topics can be found on the following pages: :class:`Filenames` -- An iterator over filenames ------------------------------------------------ -.. autoclass:: notmuch.Filenames +.. autoclass:: Filenames - .. automethod:: notmuch.Filenames.__len__ + .. automethod:: Filenames.__len__ - .. automethod:: notmuch.Filenames.as_generator + .. automethod:: Filenames.as_generator -:class:`notmuch.database.Directoy` -- A directory entry in the database +:class:`Directoy` -- A directory entry in the database ------------------------------------------------------------------------ -.. autoclass:: notmuch.Directory +.. autoclass:: Directory - .. automethod:: notmuch.Directory.get_child_files + .. automethod:: Directory.get_child_files - .. automethod:: notmuch.Directory.get_child_directories + .. automethod:: Directory.get_child_directories - .. automethod:: notmuch.Directory.get_mtime + .. automethod:: Directory.get_mtime - .. automethod:: notmuch.Directory.set_mtime + .. automethod:: Directory.set_mtime - .. autoattribute:: notmuch.Directory.mtime + .. autoattribute:: Directory.mtime - .. autoattribute:: notmuch.Directory.path + .. autoattribute:: Directory.path The `next page `_ contains information on possible Status and Error values. -- cgit v1.2.3 From 8dfbba05fa43013e9c97d94246c167c3d64a9d50 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 24 Feb 2012 01:18:54 +0100 Subject: python: move the usage example to quickstart.rst Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 14 +------------- bindings/python/docs/source/quickstart.rst | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 bindings/python/docs/source/quickstart.rst diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index fc3179b..dec9697 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -15,25 +15,13 @@ Within :mod:`notmuch`, the classes :class:`Database`, :class:`Query` provide mos This page contains the main API overview of notmuch |release|. -Notmuch can be imported as:: - - import notmuch - -or:: - - from notmuch import Query, Database - - db = Database('path',create=True) - msgs = Query(db,'from:myself').search_messages() - - for msg in msgs: - print (msg) More information on specific topics can be found on the following pages: .. toctree:: :maxdepth: 1 + quickstart status_and_errors notmuch diff --git a/bindings/python/docs/source/quickstart.rst b/bindings/python/docs/source/quickstart.rst new file mode 100644 index 0000000..609f42e --- /dev/null +++ b/bindings/python/docs/source/quickstart.rst @@ -0,0 +1,19 @@ +Quickstart and examples +======================= + +.. todo:: write a nice introduction +.. todo:: improve the examples + +Notmuch can be imported as:: + + import notmuch + +or:: + + from notmuch import Query, Database + + db = Database('path', create=True) + msgs = Query(db, 'from:myself').search_messages() + + for msg in msgs: + print(msg) -- cgit v1.2.3 From 594dbb62432bd7f92d1e121145b80628eb8aad2a Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 24 Feb 2012 01:30:04 +0100 Subject: python: move the notmuch module section to notes.rst Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/index.rst | 6 +----- bindings/python/docs/source/notes.rst | 6 ++++++ 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 bindings/python/docs/source/notes.rst diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index dec9697..a2b42aa 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -22,14 +22,10 @@ More information on specific topics can be found on the following pages: :maxdepth: 1 quickstart + notes status_and_errors notmuch -:mod:`notmuch` -- The Notmuch interface -================================================= - -.. automodule:: notmuch - :class:`Database` -- The underlying notmuch database --------------------------------------------------------------------- diff --git a/bindings/python/docs/source/notes.rst b/bindings/python/docs/source/notes.rst new file mode 100644 index 0000000..a792748 --- /dev/null +++ b/bindings/python/docs/source/notes.rst @@ -0,0 +1,6 @@ +Interfacing with notmuch +======================== + +.. todo:: move the note about talloc out of the code + +.. automodule:: notmuch -- cgit v1.2.3 From d50171d1cc575a41bf620c55853ade65d0edfad8 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 24 Feb 2012 01:34:15 +0100 Subject: python: split the documentation Move each classes documentation into its own file and thus into its own page in the generated documentation. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/docs/source/database.rst | 48 ++++++ bindings/python/docs/source/filesystem.rst | 30 ++++ bindings/python/docs/source/index.rst | 267 ++--------------------------- bindings/python/docs/source/message.rst | 54 ++++++ bindings/python/docs/source/messages.rst | 15 ++ bindings/python/docs/source/query.rst | 41 +++++ bindings/python/docs/source/tags.rst | 17 ++ bindings/python/docs/source/thread.rst | 26 +++ bindings/python/docs/source/threads.rst | 10 ++ 9 files changed, 257 insertions(+), 251 deletions(-) create mode 100644 bindings/python/docs/source/database.rst create mode 100644 bindings/python/docs/source/filesystem.rst create mode 100644 bindings/python/docs/source/message.rst create mode 100644 bindings/python/docs/source/messages.rst create mode 100644 bindings/python/docs/source/query.rst create mode 100644 bindings/python/docs/source/tags.rst create mode 100644 bindings/python/docs/source/thread.rst create mode 100644 bindings/python/docs/source/threads.rst diff --git a/bindings/python/docs/source/database.rst b/bindings/python/docs/source/database.rst new file mode 100644 index 0000000..ee71085 --- /dev/null +++ b/bindings/python/docs/source/database.rst @@ -0,0 +1,48 @@ +:class:`Database` -- The underlying notmuch database +==================================================== + +.. currentmodule:: notmuch + +.. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]]) + + .. automethod:: create + + .. automethod:: open(path, status=MODE.READ_ONLY) + + .. automethod:: get_path + + .. automethod:: get_version + + .. automethod:: needs_upgrade + + .. automethod:: upgrade + + .. automethod:: begin_atomic + + .. automethod:: end_atomic + + .. automethod:: get_directory + + .. automethod:: add_message + + .. automethod:: remove_message + + .. automethod:: find_message + + .. automethod:: find_message_by_filename + + .. automethod:: get_all_tags + + .. automethod:: create_query + + .. attribute:: Database.MODE + + Defines constants that are used as the mode in which to open a database. + + MODE.READ_ONLY + Open the database in read-only mode + + MODE.READ_WRITE + Open the database in read-write mode + + .. autoattribute:: db_p diff --git a/bindings/python/docs/source/filesystem.rst b/bindings/python/docs/source/filesystem.rst new file mode 100644 index 0000000..685dc4d --- /dev/null +++ b/bindings/python/docs/source/filesystem.rst @@ -0,0 +1,30 @@ +Files and directories +===================== + +.. currentmodule:: notmuch + +:class:`Filenames` -- An iterator over filenames +------------------------------------------------ + +.. autoclass:: Filenames + + .. automethod:: Filenames.__len__ + + .. automethod:: Filenames.as_generator + +:class:`Directoy` -- A directory entry in the database +------------------------------------------------------ + +.. autoclass:: Directory + + .. automethod:: Directory.get_child_files + + .. automethod:: Directory.get_child_directories + + .. automethod:: Directory.get_mtime + + .. automethod:: Directory.set_mtime + + .. autoattribute:: Directory.mtime + + .. autoattribute:: Directory.path diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst index a2b42aa..9ad5fa9 100644 --- a/bindings/python/docs/source/index.rst +++ b/bindings/python/docs/source/index.rst @@ -1,272 +1,37 @@ -.. notmuch documentation master file, created by - sphinx-quickstart on Tue Feb 2 10:00:47 2010. +Welcome to :mod:`notmuch`'s documentation +========================================= .. currentmodule:: notmuch -Welcome to :mod:`notmuch`'s documentation -=========================================== - -The :mod:`notmuch` module provides an interface to the `notmuch `_ functionality, directly interfacing to a shared notmuch library. -Within :mod:`notmuch`, the classes :class:`Database`, :class:`Query` provide most of the core functionality, returning :class:`Threads`, :class:`Messages` and :class:`Tags`. +The :mod:`notmuch` module provides an interface to the `notmuch +`_ functionality, directly interfacing to a +shared notmuch library. Within :mod:`notmuch`, the classes +:class:`Database`, :class:`Query` provide most of the core +functionality, returning :class:`Threads`, :class:`Messages` and +:class:`Tags`. .. moduleauthor:: Sebastian Spaeth :License: This module is covered under the GNU GPL v3 (or later). -This page contains the main API overview of notmuch |release|. - - -More information on specific topics can be found on the following pages: - .. toctree:: :maxdepth: 1 quickstart notes status_and_errors + database + query + messages + message + tags + threads + thread + filesystem notmuch -:class:`Database` -- The underlying notmuch database ---------------------------------------------------------------------- - -.. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]]) - - .. automethod:: create - - .. automethod:: open(path, status=MODE.READ_ONLY) - - .. automethod:: get_path - - .. automethod:: get_version - - .. automethod:: needs_upgrade - - .. automethod:: upgrade - - .. automethod:: begin_atomic - - .. automethod:: end_atomic - - .. automethod:: get_directory - - .. automethod:: add_message - - .. automethod:: remove_message - - .. automethod:: find_message - - .. automethod:: find_message_by_filename - - .. automethod:: get_all_tags - - .. automethod:: create_query - - .. attribute:: Database.MODE - - Defines constants that are used as the mode in which to open a database. - - MODE.READ_ONLY - Open the database in read-only mode - - MODE.READ_WRITE - Open the database in read-write mode - - .. autoattribute:: db_p - - -:class:`Query` -- A search query -------------------------------------------------- - -.. autoclass:: Query - - .. automethod:: create - - .. attribute:: Query.SORT - - Defines constants that are used as the mode in which to open a database. - - SORT.OLDEST_FIRST - Sort by message date, oldest first. - - SORT.NEWEST_FIRST - Sort by message date, newest first. - - SORT.MESSAGE_ID - Sort by email message ID. - - SORT.UNSORTED - Do not apply a special sort order (returns results in document id - order). - - .. automethod:: set_sort - - .. attribute:: sort - - Instance attribute :attr:`sort` contains the sort order (see - :attr:`Query.SORT`) if explicitely specified via - :meth:`set_sort`. By default it is set to `None`. - - .. automethod:: search_threads - - .. automethod:: search_messages - - .. automethod:: count_messages - - .. automethod:: count_threads - - -:class:`Messages` -- A bunch of messages ----------------------------------------- - -.. autoclass:: Messages - - .. automethod:: collect_tags - - .. method:: __len__() - - .. warning:: - - :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke - list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`. - -:class:`Message` -- A single message ----------------------------------------- - -.. autoclass:: Message - - .. automethod:: get_message_id - - .. automethod:: get_thread_id - - .. automethod:: get_replies - - .. automethod:: get_filename - - .. automethod:: get_filenames - - .. attribute:: FLAG - - FLAG.MATCH - This flag is automatically set by a - Query.search_threads on those messages that match the - query. This allows us to distinguish matches from the rest - of the messages in that thread. - - .. automethod:: get_flag - - .. automethod:: set_flag - - .. automethod:: get_date - - .. automethod:: get_header - - .. automethod:: get_tags - - .. automethod:: maildir_flags_to_tags - - .. automethod:: tags_to_maildir_flags - - .. automethod:: remove_tag - - .. automethod:: add_tag - - .. automethod:: remove_all_tags - - .. automethod:: freeze - - .. automethod:: thaw - - .. automethod:: format_message_as_json - - .. automethod:: format_message_as_text - - .. automethod:: __str__ - - -:class:`Tags` -- Notmuch tags ------------------------------ - -.. autoclass:: Tags - :members: - - .. method:: __len__ - - .. warning:: - - :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke - list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of - tags. - - .. automethod:: __str__ - - -:class:`Threads` -- Threads iterator ------------------------------------------------------ - -.. autoclass:: Threads - - .. automethod:: __len__ - - .. automethod:: __str__ - -:class:`Thread` -- A single thread ------------------------------------- - -.. autoclass:: Thread - - .. automethod:: get_thread_id - - .. automethod:: get_total_messages - - .. automethod:: get_toplevel_messages - - .. automethod:: get_matched_messages - - .. automethod:: get_authors - - .. automethod:: get_subject - - .. automethod:: get_oldest_date - - .. automethod:: get_newest_date - - .. automethod:: get_tags - - .. automethod:: __str__ - - -:class:`Filenames` -- An iterator over filenames ------------------------------------------------- - -.. autoclass:: Filenames - - .. automethod:: Filenames.__len__ - - .. automethod:: Filenames.as_generator - -:class:`Directoy` -- A directory entry in the database ------------------------------------------------------------------------- - -.. autoclass:: Directory - - .. automethod:: Directory.get_child_files - - .. automethod:: Directory.get_child_directories - - .. automethod:: Directory.get_mtime - - .. automethod:: Directory.set_mtime - - .. autoattribute:: Directory.mtime - - .. autoattribute:: Directory.path - - -The `next page `_ contains information on possible Status and Error values. - Indices and tables ================== * :ref:`genindex` * :ref:`search` - diff --git a/bindings/python/docs/source/message.rst b/bindings/python/docs/source/message.rst new file mode 100644 index 0000000..2ae280e --- /dev/null +++ b/bindings/python/docs/source/message.rst @@ -0,0 +1,54 @@ +:class:`Message` -- A single message +==================================== + +.. currentmodule:: notmuch + +.. autoclass:: Message + + .. automethod:: get_message_id + + .. automethod:: get_thread_id + + .. automethod:: get_replies + + .. automethod:: get_filename + + .. automethod:: get_filenames + + .. attribute:: FLAG + + FLAG.MATCH + This flag is automatically set by a + Query.search_threads on those messages that match the + query. This allows us to distinguish matches from the rest + of the messages in that thread. + + .. automethod:: get_flag + + .. automethod:: set_flag + + .. automethod:: get_date + + .. automethod:: get_header + + .. automethod:: get_tags + + .. automethod:: maildir_flags_to_tags + + .. automethod:: tags_to_maildir_flags + + .. automethod:: remove_tag + + .. automethod:: add_tag + + .. automethod:: remove_all_tags + + .. automethod:: freeze + + .. automethod:: thaw + + .. automethod:: format_message_as_json + + .. automethod:: format_message_as_text + + .. automethod:: __str__ diff --git a/bindings/python/docs/source/messages.rst b/bindings/python/docs/source/messages.rst new file mode 100644 index 0000000..3ccf505 --- /dev/null +++ b/bindings/python/docs/source/messages.rst @@ -0,0 +1,15 @@ +:class:`Messages` -- A bunch of messages +======================================== + +.. currentmodule:: notmuch + +.. autoclass:: Messages + + .. automethod:: collect_tags + + .. method:: __len__() + + .. warning:: + + :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke + list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`. diff --git a/bindings/python/docs/source/query.rst b/bindings/python/docs/source/query.rst new file mode 100644 index 0000000..ddfc348 --- /dev/null +++ b/bindings/python/docs/source/query.rst @@ -0,0 +1,41 @@ +:class:`Query` -- A search query +================================ + +.. currentmodule:: notmuch + +.. autoclass:: Query + + .. automethod:: create + + .. attribute:: Query.SORT + + Defines constants that are used as the mode in which to open a database. + + SORT.OLDEST_FIRST + Sort by message date, oldest first. + + SORT.NEWEST_FIRST + Sort by message date, newest first. + + SORT.MESSAGE_ID + Sort by email message ID. + + SORT.UNSORTED + Do not apply a special sort order (returns results in document id + order). + + .. automethod:: set_sort + + .. attribute:: sort + + Instance attribute :attr:`sort` contains the sort order (see + :attr:`Query.SORT`) if explicitely specified via + :meth:`set_sort`. By default it is set to `None`. + + .. automethod:: search_threads + + .. automethod:: search_messages + + .. automethod:: count_messages + + .. automethod:: count_threads diff --git a/bindings/python/docs/source/tags.rst b/bindings/python/docs/source/tags.rst new file mode 100644 index 0000000..31527d4 --- /dev/null +++ b/bindings/python/docs/source/tags.rst @@ -0,0 +1,17 @@ +:class:`Tags` -- Notmuch tags +----------------------------- + +.. currentmodule:: notmuch + +.. autoclass:: Tags + :members: + + .. method:: __len__ + + .. warning:: + + :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke + list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of + tags. + + .. automethod:: __str__ diff --git a/bindings/python/docs/source/thread.rst b/bindings/python/docs/source/thread.rst new file mode 100644 index 0000000..4067872 --- /dev/null +++ b/bindings/python/docs/source/thread.rst @@ -0,0 +1,26 @@ +:class:`Thread` -- A single thread +================================== + +.. currentmodule:: notmuch + +.. autoclass:: Thread + + .. automethod:: get_thread_id + + .. automethod:: get_total_messages + + .. automethod:: get_toplevel_messages + + .. automethod:: get_matched_messages + + .. automethod:: get_authors + + .. automethod:: get_subject + + .. automethod:: get_oldest_date + + .. automethod:: get_newest_date + + .. automethod:: get_tags + + .. automethod:: __str__ diff --git a/bindings/python/docs/source/threads.rst b/bindings/python/docs/source/threads.rst new file mode 100644 index 0000000..e5a8c8a --- /dev/null +++ b/bindings/python/docs/source/threads.rst @@ -0,0 +1,10 @@ +:class:`Threads` -- Threads iterator +==================================== + +.. currentmodule:: notmuch + +.. autoclass:: Threads + + .. automethod:: __len__ + + .. automethod:: __str__ -- cgit v1.2.3 From b32831e5b8e494da802f7c616c986fcd1e1e7b5f Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Wed, 1 Feb 2012 21:37:21 +0100 Subject: test: replace occurrences of $PWD with vars that are more stable Thanks to Dmitry Kurochkin for pointing this out: id:"87d39ymyb4.fsf@gmail.com" --- test/emacs | 2 +- test/new | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/emacs b/test/emacs index b74cfa9..13b0471 100755 --- a/test/emacs +++ b/test/emacs @@ -266,7 +266,7 @@ From: Notmuch Test Suite To: user@example.com Subject: Re: Testing message sent via SMTP In-Reply-To: -Fcc: $(pwd)/mail/sent +Fcc: ${MAIL_DIR}/sent --text follows this line-- On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite wrote: > This is a test that messages are sent via SMTP diff --git a/test/new b/test/new index 79a6c97..99f9913 100755 --- a/test/new +++ b/test/new @@ -117,10 +117,10 @@ test_expect_equal "$output" "No new mail. Removed 3 messages." test_begin_subtest "New symlink to directory" rm -rf "${MAIL_DIR}"/.notmuch -mv "${MAIL_DIR}" "$PWD"/actual_maildir +mv "${MAIL_DIR}" "${TMP_DIRECTORY}"/actual_maildir mkdir "${MAIL_DIR}" -ln -s "$PWD"/actual_maildir "${MAIL_DIR}"/symlink +ln -s "${TMP_DIRECTORY}"/actual_maildir "${MAIL_DIR}"/symlink output=$(NOTMUCH_NEW) test_expect_equal "$output" "Added 1 new message to the database." @@ -128,7 +128,7 @@ test_expect_equal "$output" "Added 1 new message to the database." test_begin_subtest "New symlink to a file" generate_message -external_msg_filename="$PWD"/external/"$(basename "$gen_msg_filename")" +external_msg_filename="${TMP_DIRECTORY}"/external/"$(basename "$gen_msg_filename")" mkdir -p "$(dirname "$external_msg_filename")" mv "$gen_msg_filename" "$external_msg_filename" ln -s "$external_msg_filename" "$gen_msg_filename" -- cgit v1.2.3 From cfdc9a472de43ebdb67983d911b964ec387737b5 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Thu, 9 Feb 2012 14:46:03 +0000 Subject: emacs: support text/calendar mime type Replace text/x-vcalendar with text/calendar, while maintaining support and backwards compatibility for text/x-vcalendar. Code by David Edmondson --- emacs/notmuch-show.el | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index aa9ccee..f35513b 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -729,7 +729,7 @@ current buffer, if possible." (run-hook-with-args 'notmuch-show-insert-text/plain-hook msg depth)))) t) -(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth declared-type) +(defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth declared-type) (notmuch-show-insert-part-header nth declared-type content-type (plist-get part :filename)) (insert (with-temp-buffer (insert (notmuch-show-get-bodypart-content msg part nth)) @@ -747,6 +747,10 @@ current buffer, if possible." result))) t) +;; For backwards compatibility. +(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth declared-type) + (notmuch-show-insert-part-text/calendar msg part content-type nth depth declared-type)) + (defun notmuch-show-insert-part-application/octet-stream (msg part content-type nth depth declared-type) ;; If we can deduce a MIME type from the filename of the attachment, ;; do so and pass it on to the handler for that type. -- cgit v1.2.3 From 8dec19fd66d8dafea8cbba5617eeb8ee86f4ac60 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 20:33:30 +0100 Subject: NEWS: add entry for python 3.2 compatibility Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index e9abb86..403a310 100644 --- a/NEWS +++ b/NEWS @@ -71,6 +71,13 @@ New functions notmuch_query_add_tag_exclude supports the new tag exclusion feature. +Python bindings changes +----------------------- + +Python 3.2 compatibility + + The python bindings are now compatible with both python 2.5+ and 3.2. + Build fixes ----------- -- cgit v1.2.3 From 327892f5966157427f9f9081474eefec665c37fb Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Wed, 22 Feb 2012 20:33:31 +0100 Subject: NEWS: add entry for the improved unicode handling in the python bindings Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 403a310..af5e9ea 100644 --- a/NEWS +++ b/NEWS @@ -78,6 +78,12 @@ Python 3.2 compatibility The python bindings are now compatible with both python 2.5+ and 3.2. +Added missing unicode conversions + + Python strings have to be encoded to and decoded from utf-8 when + calling libnotmuch functions. Porting the bindings to python 3.2 + revealed a few function calls that were missing these conversions. + Build fixes ----------- -- cgit v1.2.3 From 16ba777fd2a6dc6e9ade1f3894f5d7a001464a53 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Sun, 19 Feb 2012 21:50:25 +0100 Subject: emacs: `notmuch-show-get-message-id': optionally return Message-Id sans prefix * emacs/notmuch-show.el (notmuch-show-get-message-id): Add optional arg BARE. When non-nil, return a Message-Id without quotes and prefix, thus obviating the need to strip them off again in various places. (notmuch-show-stash-message-id-stripped): Update wrt changes in `notmuch-show-get-message-id'. --- emacs/notmuch-show.el | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index f35513b..529b674 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1333,9 +1333,14 @@ Some useful entries are: (notmuch-show-get-message-properties)))) (plist-get props prop))) -(defun notmuch-show-get-message-id () - "Return the message id of the current message." - (concat "id:\"" (notmuch-show-get-prop :id) "\"")) +(defun notmuch-show-get-message-id (&optional bare) + "Return the Message-Id of the current message. + +If optional argument BARE is non-nil, return +the Message-Id without prefix and quotes." + (if bare + (notmuch-show-get-prop :id) + (concat "id:\"" (notmuch-show-get-prop :id) "\""))) (defun notmuch-show-get-messages-ids () "Return all message ids of messages in the current thread." @@ -1793,7 +1798,7 @@ thread from search." (defun notmuch-show-stash-message-id-stripped () "Copy message ID of current message (sans `id:' prefix) to kill-ring." (interactive) - (notmuch-common-do-stash (substring (notmuch-show-get-message-id) 4 -1))) + (notmuch-common-do-stash (notmuch-show-get-message-id t))) (defun notmuch-show-stash-subject () "Copy Subject field of current message to kill-ring." -- cgit v1.2.3 From e2a68f9941fb903f5db7e7978ec7c6b2ad8158c1 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Sun, 19 Feb 2012 21:50:26 +0100 Subject: test: emacs: expand subtest "Stashing in notmuch-show" wrt stashing Mailing List Archive URIs `notmuch-show-stash-mlarchive-link' stashes a URI pointing to the current message at one of the MLAs configured in `notmuch-show-stash-mlarchive-link-alist'. Marked as "broken": fixed in next commit. --- test/emacs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/emacs b/test/emacs index 13b0471..e8e23dc 100755 --- a/test/emacs +++ b/test/emacs @@ -384,6 +384,7 @@ test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail. test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-with-hidden-messages test_begin_subtest "Stashing in notmuch-show" +test_subtest_known_broken add_message '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \ '[from]="Some One "' \ '[to]="Some One Else "' \ @@ -402,9 +403,12 @@ test_emacs '(notmuch-show "id:\"bought\"") (notmuch-show-stash-message-id-stripped) (notmuch-show-stash-tags) (notmuch-show-stash-filename) + (notmuch-show-stash-mlarchive-link "Gmane") + (notmuch-show-stash-mlarchive-link "MARC") + (notmuch-show-stash-mlarchive-link "Mail Archive, The") (switch-to-buffer (generate-new-buffer "*test-stashing*")) - (dotimes (i 9) + (dotimes (i 12) (yank) (insert "\n") (rotate-yank-pointer 1)) @@ -420,6 +424,9 @@ id:"bought" bought inbox,stashtest ${gen_msg_filename} +http://mid.gmane.org/bought +http://marc.info/?i=bought +http://mail-archive.com/search?l=mid&q=bought EOF test_expect_equal_file OUTPUT EXPECTED -- cgit v1.2.3 From 2f86290aaf6e4b0de54ea75ca17ce129bfbf3730 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Sun, 19 Feb 2012 21:50:27 +0100 Subject: emacs: add `notmuch-show-stash-mlarchive-link{, -and-go}' * emacs/notmuch-show.el (notmuch-show-stash-mlarchive-link-alist): New defcustom of type `alist' (key = name, value = URI), containing Mailing List Archive URI's for searching by Message-Id. (notmuch-show-stash-mlarchive-link-default): New defcustom, default MLA to use when `notmuch-show-stash-mlarchive-link' received no user input whatsoever. Available choices are generated using the contents of `notmuch-show-stash-mlarchive-link-alist'. (notmuch-show-stash-map): Added keybinds "l" and "L" for `notmuch-show-stash-mlarchive-link' respectively `notmuch-show-stash-mlarchive-link-and-go'. (notmuch-show-stash-mlarchive-link): New function, stashes a URI pointing to the current message at one of the MLAs configured in `notmuch-show-stash-mlarchive-link-alist'. Prompts user with `completing-read' if not provided with an MLA key. (notmuch-show-stash-mlarchive-link-and-go): New function, uses `notmuch-show-stash-mlarchive-link' to stash a URI, and then visits it using the browser configured in `browse-url-browser-function'. Based on original work [1] by David Edmondson . [1] id:"1327397873-20596-1-git-send-email-dme@dme.org" --- emacs/notmuch-show.el | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/emacs | 1 - 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 529b674..aa98eff 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -155,6 +155,35 @@ indentation." (make-variable-buffer-local 'notmuch-show-indent-content) (put 'notmuch-show-indent-content 'permanent-local t) +(defcustom notmuch-show-stash-mlarchive-link-alist + '(("Gmane" . "http://mid.gmane.org/") + ("MARC" . "http://marc.info/?i=") + ("Mail Archive, The" . "http://mail-archive.com/search?l=mid&q=") + ;; FIXME: can these services be searched by `Message-Id' ? + ;; ("MarkMail" . "http://markmail.org/") + ;; ("Nabble" . "http://nabble.com/") + ;; ("opensubscriber" . "http://opensubscriber.com/") + ) + "List of Mailing List Archives to use when stashing links. + +These URIs are concatenated with the current message's +Message-Id in `notmuch-show-stash-mlarchive-link'." + :type '(alist :key-type (string :tag "Name") + :value-type (string :tag "URL")) + :group 'notmuch-show) + +(defcustom notmuch-show-stash-mlarchive-link-default "Gmane" + "Default Mailing List Archive to use when stashing links. + +This is used when `notmuch-show-stash-mlarchive-link' isn't +provided with an MLA argument nor `completing-read' input." + :type `(choice + ,@(mapcar + (lambda (mla) + (list 'const :tag (car mla) :value (car mla))) + notmuch-show-stash-mlarchive-link-alist)) + :group 'notmuch-show) + (defmacro with-current-notmuch-show-message (&rest body) "Evaluate body with current buffer set to the text of current message" `(save-excursion @@ -1149,6 +1178,8 @@ buffer is stored and re-applied after the refresh." (define-key map "s" 'notmuch-show-stash-subject) (define-key map "T" 'notmuch-show-stash-tags) (define-key map "t" 'notmuch-show-stash-to) + (define-key map "l" 'notmuch-show-stash-mlarchive-link) + (define-key map "L" 'notmuch-show-stash-mlarchive-link-and-go) map) "Submap for stash commands") (fset 'notmuch-show-stash-map notmuch-show-stash-map) @@ -1815,6 +1846,36 @@ thread from search." (interactive) (notmuch-common-do-stash (notmuch-show-get-to))) +(defun notmuch-show-stash-mlarchive-link (&optional mla) + "Copy an ML Archive URI for the current message to the kill-ring. + +This presumes that the message is available at the selected Mailing List Archive. + +If optional argument MLA is non-nil, use the provided key instead of prompting +the user (see `notmuch-show-stash-mlarchive-link-alist')." + (interactive) + (notmuch-common-do-stash + (concat (cdr (assoc + (or mla + (let ((completion-ignore-case t)) + (completing-read + "Mailing List Archive: " + notmuch-show-stash-mlarchive-link-alist + nil t nil nil notmuch-show-stash-mlarchive-link-default))) + notmuch-show-stash-mlarchive-link-alist)) + (notmuch-show-get-message-id t)))) + +(defun notmuch-show-stash-mlarchive-link-and-go (&optional mla) + "Copy an ML Archive URI for the current message to the kill-ring and visit it. + +This presumes that the message is available at the selected Mailing List Archive. + +If optional argument MLA is non-nil, use the provided key instead of prompting +the user (see `notmuch-show-stash-mlarchive-link-alist')." + (interactive) + (notmuch-show-stash-mlarchive-link mla) + (browse-url (current-kill 0 t))) + ;; Commands typically bound to buttons. (defun notmuch-show-part-button-default (&optional button) diff --git a/test/emacs b/test/emacs index e8e23dc..dcfc675 100755 --- a/test/emacs +++ b/test/emacs @@ -384,7 +384,6 @@ test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail. test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-with-hidden-messages test_begin_subtest "Stashing in notmuch-show" -test_subtest_known_broken add_message '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \ '[from]="Some One "' \ '[to]="Some One Else "' \ -- cgit v1.2.3 From 4d77f18b1dbf4e6261b3d16cbbb50cb2127ebccf Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 21 Feb 2012 10:42:31 -0500 Subject: emacs: When refreshing a show buffer, only mark read when resetting state If we retain state while refreshing a show buffer, it should not mark any messages read since it's not a navigation operation (it especially shouldn't mark the first message matching the query read, which is what it did previously). If the user or caller requests that refresh reset the state of the buffer, then we consider that a navigation operation, so we do mark the message under point after the refresh read. This is implemented by moving responsibility for initial positioning and read-marking out of notmuch-show-worker and into its caller. Since notmuch-show-worker is now exclusively about building the show buffer, we rename it to notmuch-show-build-buffer. --- emacs/notmuch-show.el | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index aa98eff..6d24ed0 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1080,9 +1080,14 @@ function is used." (setq notmuch-show-thread-id thread-id notmuch-show-parent-buffer parent-buffer notmuch-show-query-context query-context) - (notmuch-show-worker))) + (notmuch-show-build-buffer) -(defun notmuch-show-worker () + ;; Move to the first open message and mark it read + (if (notmuch-show-message-visible-p) + (notmuch-show-mark-read) + (notmuch-show-next-open-message)))) + +(defun notmuch-show-build-buffer () (let ((inhibit-read-only t)) (notmuch-show-mode) @@ -1109,14 +1114,8 @@ function is used." (run-hooks 'notmuch-show-hook)) - ;; Move straight to the first open message - (unless (notmuch-show-message-visible-p) - (notmuch-show-next-open-message)) - ;; Set the header line to the subject of the first open message. - (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-pretty-subject))) - - (notmuch-show-mark-read))) + (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-pretty-subject))))) (defun notmuch-show-capture-state () "Capture the state of the current buffer. @@ -1163,9 +1162,14 @@ buffer is stored and re-applied after the refresh." (if retain-state (setq state (notmuch-show-capture-state))) (erase-buffer) - (notmuch-show-worker) + (notmuch-show-build-buffer) (if state - (notmuch-show-apply-state state)))) + (notmuch-show-apply-state state) + ;; We're resetting state, so navigate to the first open message + ;; and mark it read, just like opening a new show buffer. + (if (notmuch-show-message-visible-p) + (notmuch-show-mark-read) + (notmuch-show-next-open-message))))) (defvar notmuch-show-stash-map (let ((map (make-sparse-keymap))) -- cgit v1.2.3 From 17a06ab990217fe3f8d71d9fd2520bcfbb79d9ed Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 21 Feb 2012 10:42:32 -0500 Subject: emacs: Reverse the meaning of notmuch-show-refresh-view's argument Consensus seems to be that people prefer that refreshing show buffers retains state by default, rather than resetting it by default. This turns out to be the case in the code, as well. In fact, there's even a test for this that's been marked broken for several months, which this patch finally gets to mark as fixed. --- emacs/notmuch-crypto.el | 4 ++-- emacs/notmuch-show.el | 18 +++++++++--------- test/emacs | 1 - 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 972f26e..94da325 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -120,7 +120,7 @@ mode." :notmuch-from from) (insert "\n"))) -(declare-function notmuch-show-refresh-view "notmuch-show" (&optional retain-state)) +(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state)) (defun notmuch-crypto-sigstatus-good-callback (button) (let* ((sigstatus (button-get button :notmuch-sigstatus)) @@ -145,7 +145,7 @@ mode." (insert "\n") (call-process "gpg" nil t t "--list-keys" keyid)) (recenter -1)) - (notmuch-show-refresh-view))) + (notmuch-show-refresh-view t))) (defun notmuch-crypto-insert-encstatus-button (encstatus) (let* ((status (plist-get encstatus :status)) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 6d24ed0..dd1fb83 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -990,7 +990,7 @@ current buffer, if possible." (message (if notmuch-show-process-crypto "Processing cryptographic MIME parts." "Not processing cryptographic MIME parts.")) - (notmuch-show-refresh-view t)) + (notmuch-show-refresh-view)) (defun notmuch-show-toggle-elide-non-matching () "Toggle the display of non-matching messages." @@ -999,7 +999,7 @@ current buffer, if possible." (message (if notmuch-show-elide-non-matching-messages "Showing matching messages only." "Showing all messages.")) - (notmuch-show-refresh-view t)) + (notmuch-show-refresh-view)) (defun notmuch-show-toggle-thread-indentation () "Toggle the indentation of threads." @@ -1008,7 +1008,7 @@ current buffer, if possible." (message (if notmuch-show-indent-content "Content is indented." "Content is not indented.")) - (notmuch-show-refresh-view t)) + (notmuch-show-refresh-view)) (defun notmuch-show-insert-tree (tree depth) "Insert the message tree TREE at depth DEPTH in the current thread." @@ -1150,17 +1150,17 @@ This includes: (message "Previously current message not found.")) (notmuch-show-message-adjust))) -(defun notmuch-show-refresh-view (&optional retain-state) +(defun notmuch-show-refresh-view (&optional reset-state) "Refresh the current view. Refreshes the current view, observing changes in display -preferences. If RETAIN-STATE is non-nil then the state of the -buffer is stored and re-applied after the refresh." +preferences. If invoked with a prefix argument (or RESET-STATE is +non-nil) then the state of the buffer (open/closed messages) is +reset based on the original query." (interactive "P") (let ((inhibit-read-only t) - state) - (if retain-state - (setq state (notmuch-show-capture-state))) + (state (unless reset-state + (notmuch-show-capture-state)))) (erase-buffer) (notmuch-show-build-buffer) (if state diff --git a/test/emacs b/test/emacs index dcfc675..7549892 100755 --- a/test/emacs +++ b/test/emacs @@ -462,7 +462,6 @@ test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail. test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Refresh modified show buffer" -test_subtest_known_broken test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com") (notmuch-show-toggle-message) (notmuch-show-next-message) -- cgit v1.2.3 From 4d2d96be5f5c5d7c138a932c362e10cd9c94d665 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 21 Feb 2012 10:42:33 -0500 Subject: News for retaining state when refreshing notmuch show --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index af5e9ea..70b1518 100644 --- a/NEWS +++ b/NEWS @@ -63,6 +63,11 @@ More flexible and consistent tagging operations (notmuch-show-tag-message "-unread") +Refreshing the show view ('=' by default) no longer opens or closes messages + + To get the old behavior of putting messages back in their initial + opened/closed state, use a prefix argument, e.g., C-u =. + Library changes --------------- -- cgit v1.2.3 From 3f88b961671122e35162911b3a3276c7163d7547 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 9 Feb 2012 18:20:20 -0500 Subject: Build-Depend on libgmime-2.6-dev | libgmime2.4-dev libgmime-2.6-dev entered debian unstable today. If 2.6 is available, notmuch should build against 2.6 instead of 2.4, as 2.6 is the current upstream stable version of libgmime. --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index f6f415e..b60790e 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 7.0.50~), pkg-config, libxapian-dev, - libgmime-2.4-dev, + libgmime-2.6-dev | libgmime-2.4-dev, libtalloc-dev, libz-dev, python-all (>= 2.6.6-3~), -- cgit v1.2.3 From f531f95adcd3cc640500d8bca28b08ade5f24f62 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Sun, 19 Feb 2012 21:47:51 +0100 Subject: cli: update 'new.ignore' config file comment wrt file/directory matching --- notmuch-config.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/notmuch-config.c b/notmuch-config.c index 1f01128..e9b2750 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -47,7 +47,10 @@ static const char new_config_comment[] = "\t added to all messages incorporated by \"notmuch new\".\n" "\n" "\tignore A list (separated by ';') of file and directory names\n" - "\t that will not be searched for messages by \"notmuch new\".\n"; + "\t that will not be searched for messages by \"notmuch new\".\n" + "\n" + "\t NOTE: *Every* file/directory that goes by one of those names will\n" + "\t be ignored, independent of its depth/location in the mail store.\n"; static const char user_config_comment[] = " User configuration\n" -- cgit v1.2.3 From c5520af0ae423244afe552b4fcc16b67cf541535 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Sun, 19 Feb 2012 21:47:52 +0100 Subject: NEWS: sync 'new.ignore' entry with its comment in notmuch-config.c See previous commit. --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 70b1518..b2bec04 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,9 @@ Mail store folder/file ignore ;-separated list of file and directory names that will not be searched for messages by "notmuch new". + NOTE: *Every* file/directory that goes by one of those names will + be ignored, independent of its depth/location in the mail store. + Emacs Interface --------------- -- cgit v1.2.3 From 661c35712343408b4c034e13fc6cc8be7d580e21 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:23 -0500 Subject: Document the JSON schemata used by show and search --- devel/schemata | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ notmuch-search.c | 3 ++ notmuch-show.c | 2 + 3 files changed, 143 insertions(+) create mode 100644 devel/schemata diff --git a/devel/schemata b/devel/schemata new file mode 100644 index 0000000..24ad775 --- /dev/null +++ b/devel/schemata @@ -0,0 +1,138 @@ +This file describes the schemata used for notmuch's structured output +format (currently JSON). + +[]'s indicate lists. List items can be marked with a '?', meaning +they are optional; or a '*', meaning there can be zero or more of that +item. {}'s indicate an object that maps from field identifiers to +values. An object field marked '?' is optional. |'s indicate +alternates (e.g., int|string means something can be an int or a +string). + +Common non-terminals +-------------------- + +# Number of seconds since the Epoch +unix_time = int + +# Thread ID, sans "thread:" +threadid = string + +# Message ID, sans "id:" +messageid = string + +notmuch show schema +------------------- + +# A top-level set of threads (do_show) +# Returned by notmuch show without a --part argument +thread_set = [thread*] + +# Top-level messages in a thread (show_messages) +thread = [thread_node*] + +# A message and its replies (show_messages) +thread_node = [ + message?, # present if --entire-thread or matched + [thread_node*] # children of message +] + +# A message (show_message) +message = { + # (format_message_json) + id: messageid, + match: bool, + filename: string, + timestamp: unix_time, # date header as unix time + date_relative: string, # user-friendly timestamp + tags: [string*], + + headers: headers, + body: [part] +} + +# A MIME part (show_message_body) +part = { + # format_part_start_json + id: int|string, # part id (currently DFS part number) + + # format_part_encstatus_json + encstatus?: encstatus, + + # format_part_sigstatus_json + sigstatus?: sigstatus, + + # format_part_content_json + content-type: string, + content-id?: string, + # if content-type starts with "multipart/": + content: [part*], + # if content-type is "message/rfc822": + content: [{headers: headers, body: [part]}], + # otherwise (leaf parts): + filename?: string, + content-charset?: string, + # A leaf part's body content is optional, but may be included if + # it can be correctly encoded as a string. Consumers should use + # this in preference to fetching the part content separately. + content?: string +} + +# The headers of a message (format_headers_json with raw headers) or +# a part (format_headers_message_part_json with pretty-printed headers) +headers = { + Subject: string, + From: string, + To?: string, + Cc?: string, + Bcc?: string, + Date: string +} + +# Encryption status (format_part_encstatus_json) +encstatus = [{status: "good"|"bad"}] + +# Signature status (format_part_sigstatus_json) +sigstatus = [signature*] + +signature = { + # signature_status_to_string + status: "none"|"good"|"bad"|"error"|"unknown", + # if status is "good": + fingerprint?: string, + created?: unix_time, + expires?: unix_time, + userid?: string + # if status is not "good": + keyid?: string + # if the signature has errors: + errors?: int +} + +notmuch search schema +--------------------- + +# --output=summary +summary = [thread*] + +# --output=threads +threads = [threadid*] + +# --output=messages +messages = [messageid*] + +# --output=files +files = [string*] + +# --output=tags +tags = [string*] + +thread = { + thread: threadid, + timestamp: unix_time, + date_relative: string, # user-friendly timestamp + matched: int, # number of matched messages + total: int, # total messages in thread + authors: string, # comma-separated names with | between + # matched and unmatched + subject: string +} diff --git a/notmuch-search.c b/notmuch-search.c index d504051..92ce38a 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -90,6 +90,9 @@ format_thread_json (const void *ctx, const int total, const char *authors, const char *subject); + +/* Any changes to the JSON format should be reflected in the file + * devel/schemata. */ static const search_format_t format_json = { "[", "{", diff --git a/notmuch-show.c b/notmuch-show.c index d930f94..93fb16f 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -65,6 +65,8 @@ format_part_content_json (GMimeObject *part); static void format_part_end_json (GMimeObject *part); +/* Any changes to the JSON format should be reflected in the file + * devel/schemata. */ static const notmuch_show_format_t format_json = { "[", NULL, "{", format_message_json, -- cgit v1.2.3 From f89f3709d663ee87e0bbcfab5b184519a957d22a Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sat, 25 Feb 2012 09:50:26 -0500 Subject: emacs: Fix out of date comment The behavior of the header line in show-mode changed from showing the subject of the first open message to showing the subject of the first message in 4d77f18b. Update a comment to reflect this. --- emacs/notmuch-show.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index dd1fb83..2cca2e2 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1114,7 +1114,7 @@ function is used." (run-hooks 'notmuch-show-hook)) - ;; Set the header line to the subject of the first open message. + ;; Set the header line to the subject of the first message. (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-pretty-subject))))) (defun notmuch-show-capture-state () -- cgit v1.2.3 From bb8ab4c830aa77f7c80a45b2602a081097afba6b Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sun, 26 Feb 2012 00:23:41 +0200 Subject: man: document the notmuch configuration settings in notmuch-config(1) At the risk of duplication between the man page and the configuration file generated by default, document the notmuch configuration options in the notmuch config man page. Existing users of notmuch should not be expected to re-generate their config file in order to get access to the documentation for new configuration options. Include some minor fixes and cleanups while at it. --- man/man1/notmuch-config.1 | 125 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 17 deletions(-) diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index cb3234f..0260eb7 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -1,6 +1,6 @@ .TH NOTMUCH-CONFIG 1 2011-12-04 "Notmuch 0.10.2" .SH NAME -notmuch-config \- Output a single part of a multipart MIME message. +notmuch-config \- Access notmuch configuration file. .SH SYNOPSIS .B notmuch config get @@ -16,34 +16,125 @@ The command can be used to get or set settings in the notmuch configuration file. -.SS GET - +.RS 4 +.TP 4 +.B get The value of the specified configuration item is printed to stdout. If -the item has multiple values, each value is separated by a newline -character. +the item has multiple values (it is a list), each value is separated +by a newline character. +.RE -Available configuration items include at least +.RS 4 +.TP 4 +.B set +The specified configuration item is set to the given value. To specify +a multiple-value item (a list), provide each value as a separate +command-line argument. - database.path +If no values are provided, the specified configuration item will be +removed from the configuration file. +.RE - user.name +The available configuration items are described below. - user.primary_email +.RS 4 +.TP 4 +.B database.path +The top-level directory where your mail currently exists and to where +mail will be delivered in the future. Files should be individual email +messages. Notmuch will store its database within a sub-directory of +the path configured here named +.BR ".notmuch". +.RE - user.other_email +.RS 4 +.TP 4 +.B user.name +Your full name. +.RE - new.tags +.RS 4 +.TP 4 +.B user.primary_email +Your primary email address. +.RE -.SS SET +.RS 4 +.TP 4 +.B user.other_email +A list of other email addresses at which you receive email. +.RE -The specified configuration item is set to the given value. To -specify a multiple-value item, provide each value as a separate -command-line argument. +.RS 4 +.TP 4 +.B new.tags +A list of tags that will be added to all messages incorporated by +.BR "notmuch new". +.RE -If no values are provided, the specified configuration item will be -removed from the configuration file. +.RS 4 +.TP 4 +.B new.ignore +A list of file and directory names, without path, that will not be +searched for messages by +.BR "notmuch new". +All the files and directories matching any of the names specified here +will be ignored, regardless of the location in the mail store +directory hierarchy. .RE +.RS 4 +.TP 4 +.B search.exclude +A list of tags that will be excluded from search results by +default. Using an excluded tag in a query will override that +exclusion. +.RE + +.RS 4 +.TP 4 +.B maildir.synchronize_flags +If true, then the following maildir flags (in message filenames) will +be synchronized with the corresponding notmuch tags: + + Flag Tag + ---- ------- + D draft + F flagged + P passed + R replied + S unread (added when 'S' flag is not present) + +The +.B notmuch new +command will notice flag changes in filenames and update tags, while +the +.B notmuch tag +and +.B notmuch restore +commands will notice tag changes and update flags in filenames. + +If there have been any changes in the maildir (new messages added, old +ones removed or renamed, maildir flags changed, etc.), it is advisable +to run +.B notmuch new +before +.B notmuch tag +or +.B notmuch restore +commands to ensure the tag changes are properly synchronized to the +maildir flags, as the commands expect the database and maildir to be +in sync. +.RE + +.RE +.SH ENVIRONMENT +The following environment variables can be used to control the +behavior of notmuch. +.TP +.B NOTMUCH_CONFIG +Specifies the location of the notmuch configuration file. Notmuch will +use ${HOME}/.notmuch\-config if this variable is not set. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-count\fR(1), -- cgit v1.2.3 From bf28c60b9facb898d52759c2be993acac67c26d2 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sun, 26 Feb 2012 00:57:23 +0200 Subject: man: add references to maildir flag synchronization notmuch new, restore, and tag commands support maildir flag synchronization with notmuch tags. Reference the notmuch-config(1) man page about it in the relevant man pages. --- man/man1/notmuch-new.1 | 5 +++++ man/man1/notmuch-restore.1 | 6 ++++++ man/man1/notmuch-tag.1 | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/man/man1/notmuch-new.1 b/man/man1/notmuch-new.1 index 77d4776..76910a4 100644 --- a/man/man1/notmuch-new.1 +++ b/man/man1/notmuch-new.1 @@ -40,6 +40,11 @@ has previously been completed, but .B "notmuch new" has not previously been run. +.B "notmuch new" +updates tags according to maildir flag changes if the +.B "maildir.synchronize_flags" +configuration option is enabled. See \fBnotmuch-config\fR(1) for +details. The .B new diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1 index 2191df0..ef27938 100644 --- a/man/man1/notmuch-restore.1 +++ b/man/man1/notmuch-restore.1 @@ -29,6 +29,12 @@ dump file. See \fBnotmuch-search-terms\fR(7) for details of the supported syntax for . +.B "notmuch restore" +updates the maildir flags according to tag changes if the +.B "maildir.synchronize_flags" +configuration option is enabled. See \fBnotmuch-config\fR(1) for +details. + .RE .SH SEE ALSO diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1 index 993911b..7281bb2 100644 --- a/man/man1/notmuch-tag.1 +++ b/man/man1/notmuch-tag.1 @@ -23,6 +23,12 @@ an initial search term beginning with '+' or '\-' is provided by allowing the user to specify a "\-\-" argument to separate the tags from the search terms. +.B "notmuch tag" +updates the maildir flags according to tag changes if the +.B "maildir.synchronize_flags" +configuration option is enabled. See \fBnotmuch-config\fR(1) for +details. + .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -- cgit v1.2.3 From 95fb22ebdbd55d116b47bba47b940a95e6d463bf Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 29 Feb 2012 09:31:26 +0000 Subject: News for Make buttons for attachments allow viewing as well as saving On Sat, 21 Jan 2012 09:09:58 -0400, David Bremner wrote: > On Fri, 20 Jan 2012 09:44:06 +0000, Mark Walters wrote: > > Define a keymap for attachment buttons to allow multiple actions. > > Define 3 possible actions: > > save attachment: exactly as currently, > > view attachment: uses mailcap entry, > > view attachment with user chosen program > > pushed. Here is a News patch for this feature. Best wishes Mark From fde33258850a438f1810e4158c946b3cf185b5e8 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 29 Feb 2012 09:26:17 +0000 Subject: [PATCH] News item for allowing attachment buttons to view as well as save. --- NEWS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS b/NEWS index b2bec04..8fb2f24 100644 --- a/NEWS +++ b/NEWS @@ -71,6 +71,15 @@ Refreshing the show view ('=' by default) no longer opens or closes messages To get the old behavior of putting messages back in their initial opened/closed state, use a prefix argument, e.g., C-u =. +Attachment buttons can be used to view or save attachments. + + When the cursor is on an attachment button the key 's' can be used + to save the attachment, the key 'v' to view the attachment in the + default mailcap application, and the key 'o' prompts the user for an + application to use to open the attachment. By default Enter or mouse + button 1 saves the attachment but this is customisable (option + Notmuch Show Part Button Default Action). + Library changes --------------- -- cgit v1.2.3 From 13235d35356dd065855d4db4d44da274808407a1 Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Tue, 28 Feb 2012 19:42:03 +0100 Subject: doc: minor corrections in notmuch-config.1 * man/man1/notmuch-config.1 - SYNOPSIS: Remove spaces in 'section.item' arg, and indicate that `notmuch config set' accepts multiple value args. - DESCRIPTION: Correct config option to exclude tags from search results. ('search.exclude_tags' instead of 'search.exclude') Also see commit bb8ab4c8. --- man/man1/notmuch-config.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index 0260eb7..3d69cc6 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -4,10 +4,10 @@ notmuch-config \- Access notmuch configuration file. .SH SYNOPSIS .B notmuch config get -.RI "<" section "> . <" item ">" +.RI "<" section ">.<" item ">" .B notmuch config set -.RI "<" section "> . <" item "> [" value "]" +.RI "<" section ">.<" item "> [" value " ...]" .SH DESCRIPTION @@ -85,7 +85,7 @@ directory hierarchy. .RS 4 .TP 4 -.B search.exclude +.B search.exclude_tags A list of tags that will be excluded from search results by default. Using an excluded tag in a query will override that exclusion. -- cgit v1.2.3 From 74f8f15adc1e6fce2d3fcc34b7e9ef0b65d926db Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Fri, 24 Feb 2012 01:33:15 +0100 Subject: test: Add test for searching of uncommonly encoded messages Emails that are encoded differently than as ASCII or UTF-8 are not indexed properly by notmuch. It is not possible to search for non-ASCII words within those messages. --- test/encoding | 9 +++++++++ test/test-lib.sh | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/test/encoding b/test/encoding index 33259c1..3992b5c 100755 --- a/test/encoding +++ b/test/encoding @@ -21,4 +21,13 @@ irrelevant body} message}" +test_begin_subtest "Search for ISO-8859-2 encoded message" +test_subtest_known_broken +add_message '[content-type]="text/plain; charset=iso-8859-2"' \ + '[content-transfer-encoding]=8bit' \ + '[subject]="ISO-8859-2 encoded message"' \ + "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences +output=$(notmuch search tučňáččí 2>&1 | notmuch_show_sanitize) +test_expect_equal "$output" "thread:0000000000000002 2001-01-05 [1/1] Notmuch Test Suite; ISO-8859-2 encoded message (inbox unread)" + test_done diff --git a/test/test-lib.sh b/test/test-lib.sh index 063a2b2..2781506 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -356,6 +356,11 @@ ${additional_headers}" ${additional_headers}" fi + if [ ! -z "${template[content-transfer-encoding]}" ]; then + additional_headers="Content-Transfer-Encoding: ${template[content-transfer-encoding]} +${additional_headers}" + fi + # Note that in the way we're setting it above and using it below, # `additional_headers' will also serve as the header / body separator # (empty line in between). -- cgit v1.2.3 From 40edc971a82e236704216058591d4c7684f8058f Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Fri, 24 Feb 2012 08:36:22 +0100 Subject: Convert non-UTF-8 parts to UTF-8 before indexing them This fixes a bug that didn't allow to search for non-ASCII words such parts. The code here was copied from show_text_part_content(), because the show command already does the needed conversion when showing the message. --- lib/index.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/index.cc b/lib/index.cc index d8f8b2b..e377732 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -315,6 +315,7 @@ _index_mime_part (notmuch_message_t *message, GByteArray *byte_array; GMimeContentDisposition *disposition; char *body; + const char *charset; if (! part) { fprintf (stderr, "Warning: Not indexing empty mime part.\n"); @@ -390,6 +391,20 @@ _index_mime_part (notmuch_message_t *message, g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter), discard_uuencode_filter); + charset = g_mime_object_get_content_type_parameter (part, "charset"); + if (charset) { + GMimeFilter *charset_filter; + charset_filter = g_mime_filter_charset_new (charset, "UTF-8"); + /* This result can be NULL for things like "unknown-8bit". + * Don't set a NULL filter as that makes GMime print + * annoying assertion-failure messages on stderr. */ + if (charset_filter) { + g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter), + charset_filter); + g_object_unref (charset_filter); + } + } + wrapper = g_mime_part_get_content_object (GMIME_PART (part)); if (wrapper) g_mime_data_wrapper_write_to_stream (wrapper, filter); -- cgit v1.2.3 From 1093c24dccd5325096531aa5354b89e82307b67a Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Fri, 24 Feb 2012 08:36:23 +0100 Subject: test: Remove 'broken' flag from encoding test --- test/encoding | 1 - 1 file changed, 1 deletion(-) diff --git a/test/encoding b/test/encoding index 3992b5c..f0d073c 100755 --- a/test/encoding +++ b/test/encoding @@ -22,7 +22,6 @@ irrelevant message}" test_begin_subtest "Search for ISO-8859-2 encoded message" -test_subtest_known_broken add_message '[content-type]="text/plain; charset=iso-8859-2"' \ '[content-transfer-encoding]=8bit' \ '[subject]="ISO-8859-2 encoded message"' \ -- cgit v1.2.3 From 8c095acb6cdabde1eefcf56f308f986816a41a98 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Tue, 21 Feb 2012 10:15:08 +0100 Subject: emacs: Clarify description of thread manipulating functions It is not clear whether the term "thread" refers to the thread in the database or to the thread currently shown in a buffer. Those two meanings may refer to different sets of messages, e.g. when a new email is added to the database while the buffer shows the state before the new email arrived. This patch replaces the term thread with the term current buffer, which is hopefully less ambiguous. --- emacs/notmuch-show.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 2cca2e2..7c4c0be 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1672,7 +1672,7 @@ TAG-CHANGES is a list of tag operations for `notmuch-tag'." (apply 'notmuch-show-tag-message tag-changes))) (defun notmuch-show-tag-all (&rest tag-changes) - "Change tags for all messages in the current thread. + "Change tags for all messages in the current buffer. TAG-CHANGES is a list of tag operations for `notmuch-tag'." (interactive (notmuch-read-tag-changes nil notmuch-show-thread-id)) @@ -1765,13 +1765,13 @@ buffer." (notmuch-show-tag-all (concat op "inbox")))) (defun notmuch-show-archive-thread-then-next () - "Archive each message in thread, then show next thread from search." + "Archive all messages in the current buffer, then show next thread from search." (interactive) (notmuch-show-archive-thread) (notmuch-show-next-thread t)) (defun notmuch-show-archive-thread-then-exit () - "Archive each message in thread, then exit back to search results." + "Archive all messages in the current buffer, then exit back to search results." (interactive) (notmuch-show-archive-thread) (notmuch-show-next-thread)) -- cgit v1.2.3 From 916aefc2de504a3f658145352c690c170504c8c1 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 1 Mar 2012 07:47:21 -0400 Subject: bump version to 0.12~rc1 As usual, only `version' is edited by hand. The rest of the changes I blame on the machine. --- bindings/python/notmuch/version.py | 2 +- man/man1/notmuch-config.1 | 2 +- man/man1/notmuch-count.1 | 2 +- man/man1/notmuch-dump.1 | 2 +- man/man1/notmuch-new.1 | 2 +- man/man1/notmuch-reply.1 | 2 +- man/man1/notmuch-restore.1 | 2 +- man/man1/notmuch-search.1 | 2 +- man/man1/notmuch-show.1 | 2 +- man/man1/notmuch-tag.1 | 2 +- man/man1/notmuch.1 | 2 +- man/man5/notmuch-hooks.5 | 2 +- man/man7/notmuch-search-terms.7 | 2 +- version | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index ed40e7f..e58a1b5 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,2 +1,2 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.11.1' +__VERSION__ = '0.12~rc1' diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index 3d69cc6..e62577c 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-CONFIG 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-CONFIG 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-config \- Access notmuch configuration file. .SH SYNOPSIS diff --git a/man/man1/notmuch-count.1 b/man/man1/notmuch-count.1 index 25fe329..0d0ab5d 100644 --- a/man/man1/notmuch-count.1 +++ b/man/man1/notmuch-count.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-COUNT 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-COUNT 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-count \- Count messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-dump.1 b/man/man1/notmuch-dump.1 index 9ccf35d..bd7e274 100644 --- a/man/man1/notmuch-dump.1 +++ b/man/man1/notmuch-dump.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-DUMP 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-DUMP 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-dump \- Creates a plain-text dump of the tags of each message. diff --git a/man/man1/notmuch-new.1 b/man/man1/notmuch-new.1 index 76910a4..ccee738 100644 --- a/man/man1/notmuch-new.1 +++ b/man/man1/notmuch-new.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-NEW 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-NEW 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-new \- Incorporate new mail into the notmuch database. .SH SYNOPSIS diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index 5160ece..7ed7f0f 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-REPLY 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-REPLY 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-reply \- Constructs a reply template for a set of messages. diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1 index ef27938..8bd59e4 100644 --- a/man/man1/notmuch-restore.1 +++ b/man/man1/notmuch-restore.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-RESTORE 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-RESTORE 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-restore \- Restores the tags from the given file (see notmuch dump). diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1 index 0bc3f40..19d85df 100644 --- a/man/man1/notmuch-search.1 +++ b/man/man1/notmuch-search.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SEARCH 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-SEARCH 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-search \- Search for messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index b2301d8..4c5db94 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SHOW 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-SHOW 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-show \- Show messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1 index 7281bb2..8e0bb5c 100644 --- a/man/man1/notmuch-tag.1 +++ b/man/man1/notmuch-tag.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-TAG 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-TAG 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-tag \- Add/remove tags for all messages matching the search terms. diff --git a/man/man1/notmuch.1 b/man/man1/notmuch.1 index 424ca36..3ab1af5 100644 --- a/man/man1/notmuch.1 +++ b/man/man1/notmuch.1 @@ -16,7 +16,7 @@ .\" along with this program. If not, see http://www.gnu.org/licenses/ . .\" .\" Author: Carl Worth -.TH NOTMUCH 1 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH 1 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch \- thread-based email index, search, and tagging .SH SYNOPSIS diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5 index 2c4e552..1e459c0 100644 --- a/man/man5/notmuch-hooks.5 +++ b/man/man5/notmuch-hooks.5 @@ -1,4 +1,4 @@ -.TH NOTMUCH-HOOKS 5 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-HOOKS 5 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-hooks \- hooks for notmuch diff --git a/man/man7/notmuch-search-terms.7 b/man/man7/notmuch-search-terms.7 index a53565b..09a699f 100644 --- a/man/man7/notmuch-search-terms.7 +++ b/man/man7/notmuch-search-terms.7 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SEARCH-TERMS 7 2011-12-04 "Notmuch 0.10.2" +.TH NOTMUCH-SEARCH-TERMS 7 2012-02-29 "Notmuch 0.12~rc1" .SH NAME notmuch-search-terms \- Syntax for notmuch queries diff --git a/version b/version index af88ba8..0d4c53f 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.11.1 +0.12~rc1 -- cgit v1.2.3 From 2ddf29fe3b7f1127cd4543cd83ffd8e83871d1c4 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Thu, 1 Mar 2012 07:52:05 -0400 Subject: add minimal changelog stanza for 0.12~rc1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index fad531a..e820f89 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +notmuch (0.12~rc1-1) experimental; urgency=low + + * Upstream pre-release. + + -- David Bremner Thu, 01 Mar 2012 07:51:45 -0400 + notmuch (0.11.1-1) unstable; urgency=low * Upstream bugfix release -- cgit v1.2.3 From 3557acab6419c7b9ad9a19c5eb6c305b1184bcf8 Mon Sep 17 00:00:00 2001 From: Daniel Schoepe Date: Fri, 17 Feb 2012 18:48:07 +0400 Subject: emacs: User-defined sections in notmuch-hello This patch makes the notmuch-hello screen fully customizable by allowing the user to add and remove arbitrary sections. It also provides some convenience functions for constructing sections, e.g. showing the unread message count for each tag. This is done by specifying a list of functions that will be run when notmuch-hello is invoked. --- emacs/notmuch-hello.el | 625 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 425 insertions(+), 200 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index d17a30f..aad373d 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -154,6 +154,108 @@ International Bureau of Weights and Measures." (defvar notmuch-hello-url "http://notmuchmail.org" "The `notmuch' web site.") +(defvar notmuch-hello-search-pos nil + "Position of search widget, if any. + +This should only be set by `notmuch-hello-insert-search'.") + +(defvar notmuch-hello-custom-section-options + '((:filter (string :tag "Filter for each tag")) + (:filter-count (string :tag "Different filter to generate message counts")) + (:initially-hidden (const :tag "Hide this section on startup" t)) + (:show-empty-searches (const :tag "Show queries with no matching messages" t)) + (:hide-if-empty (const :tag "Hide this section if all queries are empty +\(and not shown by show-empty-searches)" t))) + "Various customization-options for notmuch-hello-tags/query-section.") + +(define-widget 'notmuch-hello-tags-section 'lazy + "Customize-type for notmuch-hello tag-list sections." + :tag "Customized tag-list section (see docstring for details)" + :type + `(list :tag "" + (const :tag "" notmuch-hello-insert-tags-section) + (string :tag "Title for this section") + (plist + :inline t + :options + ,(append notmuch-hello-custom-section-options + '((:hide-tags (repeat :tag "Tags that will be hidden" + string))))))) + +(define-widget 'notmuch-hello-query-section 'lazy + "Customize-type for custom saved-search-like sections" + :tag "Customized queries section (see docstring for details)" + :type + `(list :tag "" + (const :tag "" notmuch-hello-insert-query-list) + (string :tag "Title for this section") + (repeat :tag "Queries" + (cons (string :tag "Name") (string :tag "Query"))) + (plist :inline t :options ,notmuch-hello-custom-section-options))) + +(defcustom notmuch-hello-sections + (list #'notmuch-hello-insert-header + #'notmuch-hello-insert-saved-searches + #'notmuch-hello-insert-search + #'notmuch-hello-insert-recent-searches + #'notmuch-hello-insert-alltags + #'notmuch-hello-insert-footer) + "Sections for notmuch-hello. + +The list contains functions which are used to construct sections in +notmuch-hello buffer. When notmuch-hello buffer is constructed, +these functions are run in the order they appear in this list. Each +function produces a section simply by adding content to the current +buffer. A section should not end with an empty line, because a +newline will be inserted after each section by `notmuch-hello'. + +Each function should take no arguments. If the produced section +includes `notmuch-hello-target' (i.e. cursor should be positioned +inside this section), the function should return this element's +position. +Otherwise, it should return nil. + +For convenience an element can also be a list of the form (FUNC ARG1 +ARG2 .. ARGN) in which case FUNC will be applied to the rest of the +list. + +A \"Customized tag-list section\" item in the customize-interface +displays a list of all tags, optionally hiding some of them. It +is also possible to filter the list of messages matching each tag +by an additional filter query. Similarly, the count of messages +displayed next to the buttons can be generated by applying a +different filter to the tag query. These filters are also +supported for \"Customized queries section\" items." + :group 'notmuch + :type + '(repeat + (choice (function-item notmuch-hello-insert-header) + (function-item notmuch-hello-insert-saved-searches) + (function-item notmuch-hello-insert-search) + (function-item notmuch-hello-insert-recent-searches) + (function-item notmuch-hello-insert-alltags) + (function-item notmuch-hello-insert-footer) + (function-item notmuch-hello-insert-inbox) + notmuch-hello-tags-section + notmuch-hello-query-section + (function :tag "Custom section")))) + +(defvar notmuch-hello-target nil + "Button text at position of point before rebuilding the notmuch-buffer. + +This variable contains the text of the button, if any, the +point was positioned at before the notmuch-hello buffer was +rebuilt. This should never actually be global and is defined as a +defvar only for documentation purposes and to avoid a compiler +warning about it occurring as a free variable.") + +(defvar notmuch-hello-hidden-sections nil + "List of sections titles whose contents are hidden") + +(defvar notmuch-hello-first-run t + "True if `notmuch-hello' is run for the first time, set to nil +afterwards.") + (defun notmuch-hello-nice-number (n) (let (result) (while (> n 0) @@ -201,8 +303,8 @@ International Bureau of Weights and Measures." (message "Saved '%s' as '%s'." search name) (notmuch-hello-update))) -(defun notmuch-hello-longest-label (tag-alist) - (or (loop for elem in tag-alist +(defun notmuch-hello-longest-label (searches-alist) + (or (loop for elem in searches-alist maximize (length (car elem))) 0)) @@ -266,12 +368,70 @@ should be. Returns a cons cell `(tags-per-line width)'." (* tags-per-line (+ 9 1)))) tags-per-line)))) -(defun notmuch-hello-insert-tags (tag-alist widest target) - (let* ((tags-and-width (notmuch-hello-tags-per-line widest)) +(defun notmuch-hello-filtered-query (query filter) + "Constructs a query to search all messages matching QUERY and FILTER. + +If FILTER is a string, it is directly used in the returned query. + +If FILTER is a function, it is called with QUERY as a parameter and +the string it returns is used as the query. If nil is returned, +the entry is hidden. + +Otherwise, FILTER is ignored. +" + (cond + ((functionp filter) (funcall filter query)) + ((stringp filter) + (concat "(" query ") and (" filter ")")) + (t query))) + +(defun notmuch-hello-query-counts (query-alist &rest options) + "Compute list of counts of matched messages from QUERY-ALIST. + +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated query. + +The result is the list of elements of the form (NAME QUERY COUNT). + +The values :show-empty-searches, :filter and :filter-count from +options will be handled as specified for +`notmuch-hello-insert-searches'." + (notmuch-remove-if-not + #'identity + (mapcar + (lambda (elem) + (let* ((name (car elem)) + (query-and-count (if (consp (cdr elem)) + ;; do we have a different query for the message count? + (cons (second elem) (third elem)) + (cons (cdr elem) (cdr elem)))) + (message-count + (string-to-number + (notmuch-saved-search-count + (notmuch-hello-filtered-query (cdr query-and-count) + (or (plist-get options :filter-count) + (plist-get options :filter))))))) + (and (or (plist-get options :show-empty-searches) (> message-count 0)) + (list name (notmuch-hello-filtered-query + (car query-and-count) (plist-get options :filter)) + message-count)))) + query-alist))) + +(defun notmuch-hello-insert-buttons (searches) + "Insert buttons for SEARCHES. + +SEARCHES must be a list containing lists of the form (NAME QUERY COUNT), where +QUERY is the query to start when the button for the corresponding entry is +activated. COUNT should be the number of messages matching the query. +Such a list can be computed with `notmuch-hello-query-counts'." + (let* ((widest (notmuch-hello-longest-label searches)) + (tags-and-width (notmuch-hello-tags-per-line widest)) (tags-per-line (car tags-and-width)) (widest (cdr tags-and-width)) (count 0) - (reordered-list (notmuch-hello-reflect tag-alist tags-per-line)) + (reordered-list (notmuch-hello-reflect searches tags-per-line)) ;; Hack the display of the buttons used. (widget-push-button-prefix "") (widget-push-button-suffix "") @@ -281,13 +441,13 @@ should be. Returns a cons cell `(tags-per-line width)'." (mapc (lambda (elem) ;; (not elem) indicates an empty slot in the matrix. (when elem - (let* ((name (car elem)) - (query (cdr elem)) + (let* ((name (first elem)) + (query (second elem)) + (msg-count (third elem)) (formatted-name (format "%s " name))) (widget-insert (format "%8s " - (notmuch-hello-nice-number - (string-to-number (notmuch-saved-search-count query))))) - (if (string= formatted-name target) + (notmuch-hello-nice-number msg-count))) + (if (string= formatted-name notmuch-hello-target) (setq found-target-pos (point-marker))) (widget-create 'push-button :notify #'notmuch-hello-widget-search @@ -359,29 +519,241 @@ Complete list of currently available key bindings: (kill-all-local-variables) (use-local-map notmuch-hello-mode-map) (setq major-mode 'notmuch-hello-mode - mode-name "notmuch-hello") + mode-name "notmuch-hello") (run-mode-hooks 'notmuch-hello-mode-hook) ;;(setq buffer-read-only t) ) -(defun notmuch-hello-generate-tag-alist () +(defun notmuch-hello-generate-tag-alist (&optional hide-tags) "Return an alist from tags to queries to display in the all-tags section." - (notmuch-remove-if-not - #'cdr - (mapcar (lambda (tag) - (cons tag - (cond - ((functionp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - (funcall notmuch-hello-tag-list-make-query tag) ")")) - ((stringp notmuch-hello-tag-list-make-query) - (concat "tag:" tag " and (" - notmuch-hello-tag-list-make-query ")")) - (t (concat "tag:" tag))))) - (notmuch-remove-if-not - (lambda (tag) - (not (member tag notmuch-hello-hide-tags))) - (process-lines notmuch-command "search-tags"))))) + (mapcar (lambda (tag) + (cons tag (format "tag:%s" tag))) + (notmuch-remove-if-not + (lambda (tag) + (not (member tag hide-tags))) + (process-lines notmuch-command "search-tags")))) + +(defun notmuch-hello-insert-header () + "Insert the default notmuch-hello header." + (when notmuch-show-logo + (let ((image notmuch-hello-logo)) + ;; The notmuch logo uses transparency. That can display poorly + ;; when inserting the image into an emacs buffer (black logo on + ;; a black background), so force the background colour of the + ;; image. We use a face to represent the colour so that + ;; `defface' can be used to declare the different possible + ;; colours, which depend on whether the frame has a light or + ;; dark background. + (setq image (cons 'image + (append (cdr image) + (list :background (face-background 'notmuch-hello-logo-background))))) + (insert-image image)) + (widget-insert " ")) + + (widget-insert "Welcome to ") + ;; Hack the display of the links used. + (let ((widget-link-prefix "") + (widget-link-suffix "")) + (widget-create 'link + :notify (lambda (&rest ignore) + (browse-url notmuch-hello-url)) + :help-echo "Visit the notmuch website." + "notmuch") + (widget-insert ". ") + (widget-insert "You have ") + (widget-create 'link + :notify (lambda (&rest ignore) + (notmuch-hello-update)) + :help-echo "Refresh" + (notmuch-hello-nice-number + (string-to-number (car (process-lines notmuch-command "count"))))) + (widget-insert " messages.\n"))) + + +(defun notmuch-hello-insert-saved-searches () + "Insert the saved-searches section." + (let ((searches (notmuch-hello-query-counts + (if notmuch-saved-search-sort-function + (funcall notmuch-saved-search-sort-function + notmuch-saved-searches) + notmuch-saved-searches) + :show-empty-searches notmuch-show-empty-saved-searches)) + found-target-pos) + (when searches + (widget-insert "Saved searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (customize-variable 'notmuch-saved-searches)) + "edit") + (widget-insert "\n\n") + (let ((start (point))) + (setq found-target-pos + (notmuch-hello-insert-buttons searches)) + (indent-rigidly start (point) notmuch-hello-indent) + found-target-pos)))) + +(defun notmuch-hello-insert-search () + "Insert a search widget." + (widget-insert "Search: ") + (setq notmuch-hello-search-pos (point-marker)) + (widget-create 'editable-field + ;; Leave some space at the start and end of the + ;; search boxes. + :size (max 8 (- (window-width) notmuch-hello-indent + (length "Search: "))) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget)))) + ;; Add an invisible dot to make `widget-end-of-line' ignore + ;; trailing spaces in the search widget field. A dot is used + ;; instead of a space to make `show-trailing-whitespace' + ;; happy, i.e. avoid it marking the whole line as trailing + ;; spaces. + (widget-insert ".") + (put-text-property (1- (point)) (point) 'invisible t) + (widget-insert "\n")) + +(defun notmuch-hello-insert-recent-searches () + "Insert recent searches." + (when notmuch-search-history + (widget-insert "Recent searches: ") + (widget-create 'push-button + :notify (lambda (&rest ignore) + (setq notmuch-search-history nil) + (notmuch-hello-update)) + "clear") + (widget-insert "\n\n") + (let ((start (point))) + (loop for i from 1 to notmuch-hello-recent-searches-max + for search in notmuch-search-history do + (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i)))) + (set widget-symbol + (widget-create 'editable-field + ;; Don't let the search boxes be + ;; less than 8 characters wide. + :size (max 8 + (- (window-width) + ;; Leave some space + ;; at the start and + ;; end of the + ;; boxes. + (* 2 notmuch-hello-indent) + ;; 1 for the space + ;; before the + ;; `[save]' button. 6 + ;; for the `[save]' + ;; button. + 1 6)) + :action (lambda (widget &rest ignore) + (notmuch-hello-search (widget-value widget))) + search)) + (widget-insert " ") + (widget-create 'push-button + :notify (lambda (widget &rest ignore) + (notmuch-hello-add-saved-search widget)) + :notmuch-saved-search-widget widget-symbol + "save")) + (widget-insert "\n")) + (indent-rigidly start (point) notmuch-hello-indent)) + nil)) + +(defun notmuch-hello-insert-searches (title query-alist &rest options) + "Insert a section with TITLE showing a list of buttons made from QUERY-ALIST. + +QUERY-ALIST must be a list containing elements of the form (NAME . QUERY) +or (NAME QUERY COUNT-QUERY). If the latter form is used, +COUNT-QUERY specifies an alternate query to be used to generate +the count for the associated item. + +Supports the following entries in OPTIONS as a plist: +:initially-hidden - if non-nil, section will be hidden on startup +:show-empty-searches - show buttons with no matching messages +:hide-if-empty - hide if no buttons would be shown + (only makes sense without :show-empty-searches) +:filter - This can be a function that takes the search query as its argument and + returns a filter to be used in conjuction with the query for that search or nil + to hide the element. This can also be a string that is used as a combined with + each query using \"and\". +:filter-count - Separate filter to generate the count displayed each search. Accepts + the same values as :filter. If :filter and :filter-count are specified, this + will be used instead of :filter, not in conjunction with it." + (widget-insert title ": ") + (if (and notmuch-hello-first-run (plist-get options :initially-hidden)) + (add-to-list 'notmuch-hello-hidden-sections title)) + (let ((is-hidden (member title notmuch-hello-hidden-sections)) + (start (point))) + (if is-hidden + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (setq notmuch-hello-hidden-sections + (delete ,title notmuch-hello-hidden-sections)) + (notmuch-hello-update)) + "show") + (widget-create 'push-button + :notify `(lambda (widget &rest ignore) + (add-to-list 'notmuch-hello-hidden-sections + ,title) + (notmuch-hello-update)) + "hide")) + (widget-insert "\n") + (let (target-pos + (searches (apply 'notmuch-hello-query-counts query-alist options))) + (when (and (not is-hidden) + (or (not (plist-get options :hide-if-empty)) + searches)) + (widget-insert "\n") + (setq target-pos + (notmuch-hello-insert-buttons searches)) + (indent-rigidly start (point) notmuch-hello-indent) + target-pos)))) + +(defun notmuch-hello-insert-tags-section (&optional title &rest options) + "Insert a section displaying all tags with message counts. + +TITLE defaults to \"All tags\". +Allowed options are those accepted by `notmuch-hello-insert-searches' and the +following: + +:hide-tags - List of tags that should be excluded." + (apply 'notmuch-hello-insert-searches + (or title "All tags") + (notmuch-hello-generate-tag-alist (plist-get options :hide-tags)) + options)) + +(defun notmuch-hello-insert-inbox () + "Show an entry for each saved search and inboxed messages for each tag" + (notmuch-hello-insert-searches "What's in your inbox" + (append + (notmuch-saved-searches) + (notmuch-hello-generate-tag-alist)) + :filter "tag:inbox")) + +(defun notmuch-hello-insert-alltags () + "Insert a section displaying all tags and associated message counts" + (notmuch-hello-insert-tags-section + nil + :initially-hidden (not notmuch-show-all-tags-list) + :hide-tags notmuch-hello-hide-tags + :filter notmuch-hello-tag-list-make-query)) + +(defun notmuch-hello-insert-footer () + "Insert the notmuch-hello footer." + (let ((start (point))) + (widget-insert "Type a search query and hit RET to view matching threads.\n") + (when notmuch-search-history + (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") + (widget-insert "Save recent searches with the `save' button.\n")) + (when notmuch-saved-searches + (widget-insert "Edit saved searches with the `edit' button.\n")) + (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") + (widget-insert "`=' to refresh this screen. `s' to search messages. `q' to quit.\n") + (widget-create 'link + :notify (lambda (&rest ignore) + (customize-variable 'notmuch-hello-sections)) + :button-prefix "" :button-suffix "" + "Customize") + (widget-insert " this page.") + (let ((fill-column (- (window-width) notmuch-hello-indent))) + (center-region start (point))))) ;;;###autoload (defun notmuch-hello (&optional no-display) @@ -397,13 +769,13 @@ Complete list of currently available key bindings: (set-buffer "*notmuch-hello*") (switch-to-buffer "*notmuch-hello*")) - (let ((target (if (widget-at) - (widget-value (widget-at)) - (condition-case nil - (progn - (widget-forward 1) - (widget-value (widget-at))) - (error nil)))) + (let ((notmuch-hello-target (if (widget-at) + (widget-value (widget-at)) + (condition-case nil + (progn + (widget-forward 1) + (widget-value (widget-at))) + (error nil)))) (inhibit-read-only t)) ;; Delete all editable widget fields. Editable widget fields are @@ -422,168 +794,20 @@ Complete list of currently available key bindings: (mapc 'delete-overlay (car all)) (mapc 'delete-overlay (cdr all))) - (when notmuch-show-logo - (let ((image notmuch-hello-logo)) - ;; The notmuch logo uses transparency. That can display poorly - ;; when inserting the image into an emacs buffer (black logo on - ;; a black background), so force the background colour of the - ;; image. We use a face to represent the colour so that - ;; `defface' can be used to declare the different possible - ;; colours, which depend on whether the frame has a light or - ;; dark background. - (setq image (cons 'image - (append (cdr image) - (list :background (face-background 'notmuch-hello-logo-background))))) - (insert-image image)) - (widget-insert " ")) - - (widget-insert "Welcome to ") - ;; Hack the display of the links used. - (let ((widget-link-prefix "") - (widget-link-suffix "")) - (widget-create 'link - :notify (lambda (&rest ignore) - (browse-url notmuch-hello-url)) - :help-echo "Visit the notmuch website." - "notmuch") - (widget-insert ". ") - (widget-insert "You have ") - (widget-create 'link - :notify (lambda (&rest ignore) - (notmuch-hello-update)) - :help-echo "Refresh" - (notmuch-hello-nice-number - (string-to-number (car (process-lines notmuch-command "count"))))) - (widget-insert " messages.\n")) - - (let ((found-target-pos nil) - (final-target-pos nil) - (default-pos)) - (let* ((saved-alist - ;; Filter out empty saved searches if required. - (if notmuch-show-empty-saved-searches - notmuch-saved-searches - (loop for elem in notmuch-saved-searches - if (> (string-to-number (notmuch-saved-search-count (cdr elem))) 0) - collect elem))) - (saved-widest (notmuch-hello-longest-label saved-alist)) - (alltags-alist (if notmuch-show-all-tags-list (notmuch-hello-generate-tag-alist))) - (alltags-widest (notmuch-hello-longest-label alltags-alist)) - (widest (max saved-widest alltags-widest))) - - (when saved-alist - ;; Sort saved searches if required. - (when notmuch-saved-search-sort-function - (setq saved-alist - (funcall notmuch-saved-search-sort-function saved-alist))) - (widget-insert "\nSaved searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (customize-variable 'notmuch-saved-searches)) - "edit") - (widget-insert "\n\n") - (setq final-target-pos (point-marker)) - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags saved-alist widest target)) - (if found-target-pos - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\nSearch: ") - (setq default-pos (point-marker)) - (widget-create 'editable-field - ;; Leave some space at the start and end of the - ;; search boxes. - :size (max 8 (- (window-width) notmuch-hello-indent - (length "Search: "))) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget)))) - ;; Add an invisible dot to make `widget-end-of-line' ignore - ;; trailing spaces in the search widget field. A dot is used - ;; instead of a space to make `show-trailing-whitespace' - ;; happy, i.e. avoid it marking the whole line as trailing - ;; spaces. - (widget-insert ".") - (put-text-property (1- (point)) (point) 'invisible t) - (widget-insert "\n") - - (when notmuch-search-history - (widget-insert "\nRecent searches: ") - (widget-create 'push-button - :notify (lambda (&rest ignore) - (setq notmuch-search-history nil) - (notmuch-hello-update)) - "clear") - (widget-insert "\n\n") - (let ((start (point))) - (loop for i from 1 to notmuch-hello-recent-searches-max - for search in notmuch-search-history do - (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i)))) - (set widget-symbol - (widget-create 'editable-field - ;; Don't let the search boxes be - ;; less than 8 characters wide. - :size (max 8 - (- (window-width) - ;; Leave some space - ;; at the start and - ;; end of the - ;; boxes. - (* 2 notmuch-hello-indent) - ;; 1 for the space - ;; before the - ;; `[save]' button. 6 - ;; for the `[save]' - ;; button. - 1 6)) - :action (lambda (widget &rest ignore) - (notmuch-hello-search (widget-value widget))) - search)) - (widget-insert " ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (notmuch-hello-add-saved-search widget)) - :notmuch-saved-search-widget widget-symbol - "save")) - (widget-insert "\n")) - (indent-rigidly start (point) notmuch-hello-indent))) - - (when alltags-alist - (widget-insert "\nAll tags: ") - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list nil) - (notmuch-hello-update)) - "hide") - (widget-insert "\n\n") - (let ((start (point))) - (setq found-target-pos (notmuch-hello-insert-tags alltags-alist widest target)) - (unless final-target-pos - (setq final-target-pos found-target-pos)) - (indent-rigidly start (point) notmuch-hello-indent))) - - (widget-insert "\n") - - (unless notmuch-show-all-tags-list - (widget-create 'push-button - :notify (lambda (widget &rest ignore) - (setq notmuch-show-all-tags-list t) - (notmuch-hello-update)) - "Show all tags"))) - - (let ((start (point))) - (widget-insert "\n\n") - (widget-insert "Type a search query and hit RET to view matching threads.\n") - (when notmuch-search-history - (widget-insert "Hit RET to re-submit a previous search. Edit it first if you like.\n") - (widget-insert "Save recent searches with the `save' button.\n")) - (when notmuch-saved-searches - (widget-insert "Edit saved searches with the `edit' button.\n")) - (widget-insert "Hit RET or click on a saved search or tag name to view matching threads.\n") - (widget-insert "`=' to refresh this screen. `s' to search messages. `q' to quit.\n") - (let ((fill-column (- (window-width) notmuch-hello-indent))) - (center-region start (point)))) - + (let (final-target-pos) + (mapc + (lambda (section) + (let ((point-before (point)) + (result (if (functionp section) + (funcall section) + (apply (car section) (cdr section))))) + (if (and (not final-target-pos) (integer-or-marker-p result)) + (setq final-target-pos result)) + ;; don't insert a newline when the previous section didn't show + ;; anything. + (unless (eq (point) point-before) + (widget-insert "\n")))) + notmuch-hello-sections) (widget-setup) (when final-target-pos @@ -592,9 +816,10 @@ Complete list of currently available key bindings: (widget-forward 1))) (unless (widget-at) - (goto-char default-pos)))) - - (run-hooks 'notmuch-hello-refresh-hook)) + (when notmuch-hello-search-pos + (goto-char notmuch-hello-search-pos))))) + (run-hooks 'notmuch-hello-refresh-hook) + (setq notmuch-hello-first-run nil)) (defun notmuch-folder () "Deprecated function for invoking notmuch---calling `notmuch' is preferred now." -- cgit v1.2.3 From 6e25ea031fc43dd3a6a9ff00f33526fdd3abc92a Mon Sep 17 00:00:00 2001 From: Daniel Schoepe Date: Fri, 17 Feb 2012 18:48:08 +0400 Subject: emacs: Tests for user-defined sections A new file was added for notmuch-hello tests. --- test/emacs-hello | 47 ++++++++++++++++++++++ test/emacs.expected-output/notmuch-hello | 3 +- .../notmuch-hello-new-section | 4 ++ .../notmuch-hello-no-saved-searches | 3 +- .../notmuch-hello-section-counts | 5 +++ .../notmuch-hello-section-hidden-tag | 4 ++ .../notmuch-hello-section-with-empty | 4 ++ .../emacs.expected-output/notmuch-hello-with-empty | 3 +- test/notmuch-test | 1 + 9 files changed, 71 insertions(+), 3 deletions(-) create mode 100755 test/emacs-hello create mode 100644 test/emacs.expected-output/notmuch-hello-new-section create mode 100644 test/emacs.expected-output/notmuch-hello-section-counts create mode 100644 test/emacs.expected-output/notmuch-hello-section-hidden-tag create mode 100644 test/emacs.expected-output/notmuch-hello-section-with-empty diff --git a/test/emacs-hello b/test/emacs-hello new file mode 100755 index 0000000..b235e3a --- /dev/null +++ b/test/emacs-hello @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +test_description="Testing emacs notmuch-hello view" +. test-lib.sh + +EXPECTED=$TEST_DIRECTORY/emacs.expected-output + +add_email_corpus + +test_begin_subtest "User-defined section with inbox tag" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-searches + \"Test\" '((\"inbox\" . \"tag:inbox\"))))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-new-section + +test_begin_subtest "User-defined section with empty, hidden entry" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-searches + \"Test-with-empty\" + '((\"inbox\" . \"tag:inbox\") + (\"doesnotexist\" . \"tag:doesnotexist\")) + :hide-empty-searches t))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-with-empty + +test_begin_subtest "User-defined section, unread tag filtered out" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + \"Test-with-filtered\" + :hide-tags '(\"unread\")))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-hidden-tag + +test_begin_subtest "User-defined section, different query for counts" +test_emacs "(let ((notmuch-hello-sections + (list (lambda () (notmuch-hello-insert-tags-section + \"Test-with-counts\" + :filter-count \"tag:signed\"))))) + (notmuch-hello) + (test-output))" +test_expect_equal_file OUTPUT $EXPECTED/notmuch-hello-section-counts + +test_done diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello index 3e59595..1470790 100644 --- a/test/emacs.expected-output/notmuch-hello +++ b/test/emacs.expected-output/notmuch-hello @@ -6,9 +6,10 @@ Saved searches: [edit] Search: . -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' to refresh this screen. `s' to search messages. `q' to quit. + Customize this page. diff --git a/test/emacs.expected-output/notmuch-hello-new-section b/test/emacs.expected-output/notmuch-hello-new-section new file mode 100644 index 0000000..c64d712 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-new-section @@ -0,0 +1,4 @@ +Test: [hide] + + 52 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches index ef0e5d0..05475b1 100644 --- a/test/emacs.expected-output/notmuch-hello-no-saved-searches +++ b/test/emacs.expected-output/notmuch-hello-no-saved-searches @@ -2,9 +2,10 @@ Search: . -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' to refresh this screen. `s' to search messages. `q' to quit. + Customize this page. diff --git a/test/emacs.expected-output/notmuch-hello-section-counts b/test/emacs.expected-output/notmuch-hello-section-counts new file mode 100644 index 0000000..9d79659 --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-counts @@ -0,0 +1,5 @@ +Test-with-counts: [hide] + + 2 attachment 7 signed + 7 inbox 7 unread + diff --git a/test/emacs.expected-output/notmuch-hello-section-hidden-tag b/test/emacs.expected-output/notmuch-hello-section-hidden-tag new file mode 100644 index 0000000..3688e7c --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-hidden-tag @@ -0,0 +1,4 @@ +Test-with-filtered: [hide] + + 4 attachment 52 inbox 7 signed + diff --git a/test/emacs.expected-output/notmuch-hello-section-with-empty b/test/emacs.expected-output/notmuch-hello-section-with-empty new file mode 100644 index 0000000..8209fed --- /dev/null +++ b/test/emacs.expected-output/notmuch-hello-section-with-empty @@ -0,0 +1,4 @@ +Test-with-empty: [hide] + + 52 inbox + diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty index 71edba7..5e53222 100644 --- a/test/emacs.expected-output/notmuch-hello-with-empty +++ b/test/emacs.expected-output/notmuch-hello-with-empty @@ -6,9 +6,10 @@ Saved searches: [edit] Search: . -[Show all tags] +All tags: [show] Type a search query and hit RET to view matching threads. Edit saved searches with the `edit' button. Hit RET or click on a saved search or tag name to view matching threads. `=' to refresh this screen. `s' to search messages. `q' to quit. + Customize this page. diff --git a/test/notmuch-test b/test/notmuch-test index e14d34e..f03b594 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -54,6 +54,7 @@ TESTS=" argument-parsing emacs-test-functions emacs-address-cleaning + emacs-hello emacs-show " TESTS=${NOTMUCH_TESTS:=$TESTS} -- cgit v1.2.3 From 44d9656cbf51ade90e372eadbf3542de83549c74 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:24 -0500 Subject: show: Convert JSON format to the new self-recursive style As before, this is all code movement and a smidgen of glue. This moves the existing JSON formatter code into one self-recursive function, but doesn't change any of the logic to take advantage of the new structure. In general, "leafs" of the JSON structure are left in helper functions (most of them untouched), so that it's easy to see the overall structure of the format from the main recursive function. --- notmuch-show.c | 273 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 135 insertions(+), 138 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 93fb16f..868b2cd 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -35,52 +35,14 @@ static const notmuch_show_format_t format_text = { }; static void -format_message_json (const void *ctx, - notmuch_message_t *message, - unused (int indent)); -static void -format_headers_json (const void *ctx, - notmuch_message_t *message); - -static void -format_headers_message_part_json (GMimeMessage *message); - -static void -format_part_start_json (unused (GMimeObject *part), - int *part_count); - -static void -format_part_encstatus_json (int status); - -static void -#ifdef GMIME_ATLEAST_26 -format_part_sigstatus_json (GMimeSignatureList* siglist); -#else -format_part_sigstatus_json (const GMimeSignatureValidity* validity); -#endif +format_part_json_entry (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params); -static void -format_part_content_json (GMimeObject *part); - -static void -format_part_end_json (GMimeObject *part); - -/* Any changes to the JSON format should be reflected in the file - * devel/schemata. */ static const notmuch_show_format_t format_json = { - "[", NULL, - "{", format_message_json, - "\"headers\": {", format_headers_json, format_headers_message_part_json, "}", - ", \"body\": [", - format_part_start_json, - format_part_encstatus_json, - format_part_sigstatus_json, - format_part_content_json, - format_part_end_json, - ", ", - "]", - "}", ", ", - "]" + .message_set_start = "[", + .part = format_part_json_entry, + .message_set_sep = ", ", + .message_set_end = "]" }; static void @@ -170,7 +132,7 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message) } static void -format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent)) +format_message_json (const void *ctx, notmuch_message_t *message) { notmuch_tags_t *tags; int first = 1; @@ -471,24 +433,6 @@ signer_status_to_string (GMimeSignerStatus x) } #endif -static void -format_part_start_json (unused (GMimeObject *part), int *part_count) -{ - printf ("{\"id\": %d", *part_count); -} - -static void -format_part_encstatus_json (int status) -{ - printf (", \"encstatus\": [{\"status\": "); - if (status) { - printf ("\"good\""); - } else { - printf ("\"bad\""); - } - printf ("}]"); -} - #ifdef GMIME_ATLEAST_26 static void format_part_sigstatus_json (GMimeSignatureList *siglist) @@ -618,81 +562,6 @@ format_part_sigstatus_json (const GMimeSignatureValidity* validity) } #endif -static void -format_part_content_json (GMimeObject *part) -{ - GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); - GMimeStream *stream_memory = g_mime_stream_mem_new (); - const char *cid = g_mime_object_get_content_id (part); - void *ctx = talloc_new (NULL); - GByteArray *part_content; - - printf (", \"content-type\": %s", - json_quote_str (ctx, g_mime_content_type_to_string (content_type))); - - if (cid != NULL) - printf(", \"content-id\": %s", json_quote_str (ctx, cid)); - - if (GMIME_IS_PART (part)) - { - const char *filename = g_mime_part_get_filename (GMIME_PART (part)); - if (filename) - printf (", \"filename\": %s", json_quote_str (ctx, filename)); - } - - if (g_mime_content_type_is_type (content_type, "text", "*")) - { - /* For non-HTML text parts, we include the content in the - * JSON. Since JSON must be Unicode, we handle charset - * decoding here and do not report a charset to the caller. - * For text/html parts, we do not include the content. If a - * caller is interested in text/html parts, it should retrieve - * them separately and they will not be decoded. Since this - * makes charset decoding the responsibility on the caller, we - * report the charset for text/html parts. - */ - if (g_mime_content_type_is_type (content_type, "text", "html")) - { - const char *content_charset = g_mime_object_get_content_type_parameter (GMIME_OBJECT (part), "charset"); - - if (content_charset != NULL) - printf (", \"content-charset\": %s", json_quote_str (ctx, content_charset)); - } - else - { - show_text_part_content (part, stream_memory); - part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); - - printf (", \"content\": %s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len)); - } - } - else if (g_mime_content_type_is_type (content_type, "multipart", "*")) - { - printf (", \"content\": ["); - } - else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) - { - printf (", \"content\": [{"); - } - - talloc_free (ctx); - if (stream_memory) - g_object_unref (stream_memory); -} - -static void -format_part_end_json (GMimeObject *part) -{ - GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part)); - - if (g_mime_content_type_is_type (content_type, "multipart", "*")) - printf ("]"); - else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) - printf ("}]"); - - printf ("}"); -} - static void format_part_content_raw (GMimeObject *part) { @@ -810,6 +679,134 @@ format_part_text (const void *ctx, mime_node_t *node, printf ("\f%s}\n", part_type); } +static void +format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) +{ + /* Any changes to the JSON format should be reflected in the file + * devel/schemata. */ + + if (node->envelope_file) { + printf ("{"); + format_message_json (ctx, node->envelope_file); + + printf ("\"headers\": {"); + format_headers_json (ctx, node->envelope_file); + printf ("}"); + + printf (", \"body\": ["); + format_part_json (ctx, mime_node_child (node, 0), first); + + printf ("]}"); + return; + } + + void *local = talloc_new (ctx); + /* The disposition and content-type metadata are associated with + * the envelope for message parts */ + GMimeObject *meta = node->envelope_part ? + GMIME_OBJECT (node->envelope_part) : node->part; + GMimeContentType *content_type = g_mime_object_get_content_type (meta); + GMimeStream *stream_memory = g_mime_stream_mem_new (); + const char *cid = g_mime_object_get_content_id (meta); + GByteArray *part_content; + int i; + + if (!first) + printf (", "); + + printf ("{\"id\": %d", node->part_num); + + if (node->decrypt_attempted) { + printf (", \"encstatus\": [{\"status\": "); + if (node->decrypt_success) { + printf ("\"good\""); + } else { + printf ("\"bad\""); + } + printf ("}]"); + } + + if (node->verify_attempted) { +#ifdef GMIME_ATLEAST_26 + format_part_sigstatus_json (node->sig_list); +#else + format_part_sigstatus_json (node->sig_validity); +#endif + } + + printf (", \"content-type\": %s", + json_quote_str (local, g_mime_content_type_to_string (content_type))); + + if (cid != NULL) + printf(", \"content-id\": %s", json_quote_str (local, cid)); + + if (GMIME_IS_PART (node->part)) { + const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); + if (filename) + printf (", \"filename\": %s", json_quote_str (local, filename)); + } + + if (g_mime_content_type_is_type (content_type, "text", "*")) { + /* For non-HTML text parts, we include the content in the + * JSON. Since JSON must be Unicode, we handle charset + * decoding here and do not report a charset to the caller. + * For text/html parts, we do not include the content. If a + * caller is interested in text/html parts, it should retrieve + * them separately and they will not be decoded. Since this + * makes charset decoding the responsibility on the caller, we + * report the charset for text/html parts. + */ + if (g_mime_content_type_is_type (content_type, "text", "html")) { + const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset"); + + if (content_charset != NULL) + printf (", \"content-charset\": %s", json_quote_str (local, content_charset)); + } else { + show_text_part_content (node->part, stream_memory); + part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); + + printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len)); + } + } else if (g_mime_content_type_is_type (content_type, "multipart", "*")) { + printf (", \"content\": ["); + } else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) { + printf (", \"content\": [{"); + } + + if (stream_memory) + g_object_unref (stream_memory); + + if (GMIME_IS_MESSAGE (node->part)) { + printf ("\"headers\": {"); + format_headers_message_part_json (GMIME_MESSAGE (node->part)); + printf ("}"); + + printf (", \"body\": ["); + } + + for (i = 0; i < node->nchildren; i++) + format_part_json (ctx, mime_node_child (node, i), i == 0); + + if (GMIME_IS_MESSAGE (node->part)) + printf ("]"); + + if (g_mime_content_type_is_type (content_type, "multipart", "*")) + printf ("]"); + else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) + printf ("}]"); + + printf ("}"); + + talloc_free (local); +} + +static void +format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), + unused (const notmuch_show_params_t *params)) +{ + format_part_json (ctx, node, TRUE); +} + static void show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From 2209d7b9520000fbd6941b1e0e8521da90f443c6 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:25 -0500 Subject: show: Use consistent header ordering in the JSON format Previously, top-level message headers were printed as Subject, From, To, Date, while embedded message headers were printed From, To, Subject, Date. This makes both cases use the former order and updates the tests accordingly. --- notmuch-show.c | 6 +++--- test/multipart | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 868b2cd..9ca9882 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -328,6 +328,9 @@ format_headers_message_part_json (GMimeMessage *message) const char *recipients_string; printf ("%s: %s", + json_quote_str (ctx_quote, "Subject"), + json_quote_str (ctx_quote, g_mime_message_get_subject (message))); + printf (", %s: %s", json_quote_str (ctx_quote, "From"), json_quote_str (ctx_quote, g_mime_message_get_sender (message))); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); @@ -342,9 +345,6 @@ format_headers_message_part_json (GMimeMessage *message) printf (", %s: %s", json_quote_str (ctx_quote, "Cc"), json_quote_str (ctx_quote, recipients_string)); - printf (", %s: %s", - json_quote_str (ctx_quote, "Subject"), - json_quote_str (ctx_quote, g_mime_message_get_subject (message))); printf (", %s: %s", json_quote_str (ctx_quote, "Date"), json_quote_str (ctx_quote, g_mime_message_get_date_as_string (message))); diff --git a/test/multipart b/test/multipart index 2dd73f5..4d14804 100755 --- a/test/multipart +++ b/test/multipart @@ -325,7 +325,7 @@ cat <EXPECTED {"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Cc": "", "Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, @@ -342,7 +342,7 @@ cat <EXPECTED {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, @@ -358,7 +358,7 @@ echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED {"id": 2, "content-type": "multipart/mixed", "content": [ -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]}, @@ -372,7 +372,7 @@ notmuch show --format=json --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' | s echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED -{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"From": "Carl Worth ", "To": "cworth@cworth.org", "Subject": "html message", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ +{"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ {"id": 4, "content-type": "multipart/alternative", "content": [ {"id": 5, "content-type": "text/html"}, {"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}]}]}]} -- cgit v1.2.3 From 86f89385c3bc34cd91002cc057f6a615b6ab76a9 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:26 -0500 Subject: show: Unify JSON header output for messages and message parts This has three ramifications: - Blank To and Cc headers are no longer output for messages. - Dates are now canonicalized for messages, which means they always have a day of the week and GMT is printed +0000 (never -0000) - Invalid From message headers are handled slightly differently, since they get parsed by GMime now instead of notmuch. --- notmuch-show.c | 35 +++-------------------------------- test/crypto | 35 ++++++++++++++--------------------- test/emacs | 4 ++-- test/json | 6 +++--- test/maildir-sync | 2 -- test/multipart | 2 +- 6 files changed, 23 insertions(+), 61 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 9ca9882..209ff45 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -291,36 +291,7 @@ format_headers_message_part_text (GMimeMessage *message) } static void -format_headers_json (const void *ctx, notmuch_message_t *message) -{ - const char *headers[] = { - "Subject", "From", "To", "Cc", "Bcc", "Date" - }; - const char *name, *value; - unsigned int i; - int first_header = 1; - void *ctx_quote = talloc_new (ctx); - - for (i = 0; i < ARRAY_SIZE (headers); i++) { - name = headers[i]; - value = notmuch_message_get_header (message, name); - if (value) - { - if (!first_header) - fputs (", ", stdout); - first_header = 0; - - printf ("%s: %s", - json_quote_str (ctx_quote, name), - json_quote_str (ctx_quote, value)); - } - } - - talloc_free (ctx_quote); -} - -static void -format_headers_message_part_json (GMimeMessage *message) +format_headers_json (GMimeMessage *message) { void *ctx = talloc_new (NULL); void *ctx_quote = talloc_new (ctx); @@ -690,7 +661,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_message_json (ctx, node->envelope_file); printf ("\"headers\": {"); - format_headers_json (ctx, node->envelope_file); + format_headers_json (GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); @@ -778,7 +749,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (GMIME_IS_MESSAGE (node->part)) { printf ("\"headers\": {"); - format_headers_message_part_json (GMIME_MESSAGE (node->part)); + format_headers_json (GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); diff --git a/test/crypto b/test/crypto index 1dbb60a..7e774c8 100755 --- a/test/crypto +++ b/test/crypto @@ -50,9 +50,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "good", "fingerprint": "'$FINGERPRINT'", @@ -84,9 +83,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "good", "fingerprint": "'$FINGERPRINT'", @@ -120,9 +118,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "error", "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'", @@ -194,9 +191,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test encrypted message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "encstatus": [{"status": "good"}], "sigstatus": [], @@ -249,9 +245,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test encrypted message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "encstatus": [{"status": "bad"}], "content-type": "multipart/encrypted", @@ -284,9 +279,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test encrypted message 002", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "encstatus": [{"status": "good"}], "sigstatus": [{"status": "good", @@ -339,9 +333,8 @@ expected='[[[{"id": "XXXXX", "headers": {"Subject": "test signed message 001", "From": "Notmuch Test Suite ", "To": "test_suite@notmuchmail.org", - "Cc": "", - "Bcc": "", - "Date": "01 Jan 2000 12:00:00 -0000"}, + "Date": "Sat, + 01 Jan 2000 12:00:00 +0000"}, "body": [{"id": 1, "sigstatus": [{"status": "error", "keyid": "6D92612D94E46381", diff --git a/test/emacs b/test/emacs index 7549892..29a489c 100755 --- a/test/emacs +++ b/test/emacs @@ -78,7 +78,7 @@ thread=$(notmuch search --output=threads subject:message-with-invalid-from) test_emacs "(notmuch-show \"$thread\") (test-output)" cat <EXPECTED -Invalid " From (2001-01-05) (inbox) +"Invalid " (2001-01-05) (inbox) Subject: message-with-invalid-from To: Notmuch Test Suite Date: Fri, 05 Jan 2001 15:43:57 +0000 @@ -414,7 +414,7 @@ test_emacs '(notmuch-show "id:\"bought\"") (reverse-region (point-min) (point-max)) (test-output)' cat <EXPECTED -Sat, 01 Jan 2000 12:00:00 -0000 +Sat, 01 Jan 2000 12:00:00 +0000 Some One Some One Else Notmuch diff --git a/test/json b/test/json index 7df4380..1bdffd2 100755 --- a/test/json +++ b/test/json @@ -5,7 +5,7 @@ test_description="--format=json output" test_begin_subtest "Show message: json" add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-show-message\"" output=$(notmuch show --format=json "json-show-message") -test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]" test_begin_subtest "Search message: json" add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-search-message\"" @@ -22,7 +22,7 @@ test_expect_equal "$output" "[{\"thread\": \"XXX\", test_begin_subtest "Show message: json, utf-8" add_message "[subject]=\"json-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\"" output=$(notmuch show --format=json "jsön-show-méssage") -test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]" test_begin_subtest "Show message: json, inline attachment filename" subject='json-show-inline-attachment-filename' @@ -35,7 +35,7 @@ emacs_deliver_message \ (insert \"Message-ID: <$id>\n\")" output=$(notmuch show --format=json "id:$id") filename=$(notmuch search --output=files "id:$id") -test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"test_suite@notmuchmail.org\", \"Cc\": \"\", \"Bcc\": \"\", \"Date\": \"01 Jan 2000 12:00:00 -0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]" test_begin_subtest "Search message: json, utf-8" add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\"" diff --git a/test/maildir-sync b/test/maildir-sync index d5872a5..1ee2db0 100755 --- a/test/maildir-sync +++ b/test/maildir-sync @@ -53,8 +53,6 @@ test_expect_equal "$output" '[[[{"id": "adding-replied-tag@notmuch-test-suite", "headers": {"Subject": "Adding replied tag", "From": "Notmuch Test Suite ", "To": "Notmuch Test Suite ", -"Cc": "", -"Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1, diff --git a/test/multipart b/test/multipart index 4d14804..a3036b4 100755 --- a/test/multipart +++ b/test/multipart @@ -322,7 +322,7 @@ notmuch show --format=json --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' | s echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED -{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Cc": "", "Bcc": "", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ +{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ {"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ -- cgit v1.2.3 From 63ee244c8e9fd1fdd63697f3584530499157652f Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:27 -0500 Subject: show: Simplify talloc use in format_headers_json Previously there was an unnecessary talloc context. --- notmuch-show.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 209ff45..6259d30 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -291,37 +291,35 @@ format_headers_message_part_text (GMimeMessage *message) } static void -format_headers_json (GMimeMessage *message) +format_headers_json (const void *ctx, GMimeMessage *message) { - void *ctx = talloc_new (NULL); - void *ctx_quote = talloc_new (ctx); + void *local = talloc_new (ctx); InternetAddressList *recipients; const char *recipients_string; printf ("%s: %s", - json_quote_str (ctx_quote, "Subject"), - json_quote_str (ctx_quote, g_mime_message_get_subject (message))); + json_quote_str (local, "Subject"), + json_quote_str (local, g_mime_message_get_subject (message))); printf (", %s: %s", - json_quote_str (ctx_quote, "From"), - json_quote_str (ctx_quote, g_mime_message_get_sender (message))); + json_quote_str (local, "From"), + json_quote_str (local, g_mime_message_get_sender (message))); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); recipients_string = internet_address_list_to_string (recipients, 0); if (recipients_string) printf (", %s: %s", - json_quote_str (ctx_quote, "To"), - json_quote_str (ctx_quote, recipients_string)); + json_quote_str (local, "To"), + json_quote_str (local, recipients_string)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); recipients_string = internet_address_list_to_string (recipients, 0); if (recipients_string) printf (", %s: %s", - json_quote_str (ctx_quote, "Cc"), - json_quote_str (ctx_quote, recipients_string)); + json_quote_str (local, "Cc"), + json_quote_str (local, recipients_string)); printf (", %s: %s", - json_quote_str (ctx_quote, "Date"), - json_quote_str (ctx_quote, g_mime_message_get_date_as_string (message))); + json_quote_str (local, "Date"), + json_quote_str (local, g_mime_message_get_date_as_string (message))); - talloc_free (ctx_quote); - talloc_free (ctx); + talloc_free (local); } /* Write a MIME text part out to the given stream. @@ -661,7 +659,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_message_json (ctx, node->envelope_file); printf ("\"headers\": {"); - format_headers_json (GMIME_MESSAGE (node->part)); + format_headers_json (ctx, GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); @@ -749,7 +747,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (GMIME_IS_MESSAGE (node->part)) { printf ("\"headers\": {"); - format_headers_json (GMIME_MESSAGE (node->part)); + format_headers_json (local, GMIME_MESSAGE (node->part)); printf ("}"); printf (", \"body\": ["); -- cgit v1.2.3 From 1f0ead385a11422be4a06c574d8836eaa65a5d39 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:28 -0500 Subject: show: Make JSON helper functions print complete objects This makes the main recursive function easier to follow because helper functions don't add fields to the running object. --- notmuch-show.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 6259d30..8fb6fa6 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -297,7 +297,7 @@ format_headers_json (const void *ctx, GMimeMessage *message) InternetAddressList *recipients; const char *recipients_string; - printf ("%s: %s", + printf ("{%s: %s", json_quote_str (local, "Subject"), json_quote_str (local, g_mime_message_get_subject (message))); printf (", %s: %s", @@ -315,7 +315,7 @@ format_headers_json (const void *ctx, GMimeMessage *message) printf (", %s: %s", json_quote_str (local, "Cc"), json_quote_str (local, recipients_string)); - printf (", %s: %s", + printf (", %s: %s}", json_quote_str (local, "Date"), json_quote_str (local, g_mime_message_get_date_as_string (message))); @@ -406,7 +406,7 @@ signer_status_to_string (GMimeSignerStatus x) static void format_part_sigstatus_json (GMimeSignatureList *siglist) { - printf (", \"sigstatus\": ["); + printf ("["); if (!siglist) { printf ("]"); @@ -472,7 +472,7 @@ format_part_sigstatus_json (GMimeSignatureList *siglist) static void format_part_sigstatus_json (const GMimeSignatureValidity* validity) { - printf (", \"sigstatus\": ["); + printf ("["); if (!validity) { printf ("]"); @@ -658,9 +658,8 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("{"); format_message_json (ctx, node->envelope_file); - printf ("\"headers\": {"); + printf ("\"headers\": "); format_headers_json (ctx, GMIME_MESSAGE (node->part)); - printf ("}"); printf (", \"body\": ["); format_part_json (ctx, mime_node_child (node, 0), first); @@ -696,6 +695,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) } if (node->verify_attempted) { + printf (", \"sigstatus\": "); #ifdef GMIME_ATLEAST_26 format_part_sigstatus_json (node->sig_list); #else @@ -746,9 +746,8 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) g_object_unref (stream_memory); if (GMIME_IS_MESSAGE (node->part)) { - printf ("\"headers\": {"); + printf ("\"headers\": "); format_headers_json (local, GMIME_MESSAGE (node->part)); - printf ("}"); printf (", \"body\": ["); } -- cgit v1.2.3 From 99789e77f2a18fd89198fd9cebdb7a756dd367e3 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:29 -0500 Subject: show: Make format_part_sigstatus_json's API consistent between GMIME 2.4 and 2.6 The implementation is still different for GMIME 2.4 and 2.6, but at least now the caller doesn't have to be aware of this. --- notmuch-show.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 8fb6fa6..07276c7 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -404,8 +404,10 @@ signer_status_to_string (GMimeSignerStatus x) #ifdef GMIME_ATLEAST_26 static void -format_part_sigstatus_json (GMimeSignatureList *siglist) +format_part_sigstatus_json (mime_node_t *node) { + GMimeSignatureList *siglist = node->sig_list; + printf ("["); if (!siglist) { @@ -470,8 +472,10 @@ format_part_sigstatus_json (GMimeSignatureList *siglist) } #else static void -format_part_sigstatus_json (const GMimeSignatureValidity* validity) +format_part_sigstatus_json (mime_node_t *node) { + const GMimeSignatureValidity* validity = node->sig_validity; + printf ("["); if (!validity) { @@ -696,11 +700,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (node->verify_attempted) { printf (", \"sigstatus\": "); -#ifdef GMIME_ATLEAST_26 - format_part_sigstatus_json (node->sig_list); -#else - format_part_sigstatus_json (node->sig_validity); -#endif + format_part_sigstatus_json (node); } printf (", \"content-type\": %s", -- cgit v1.2.3 From 4fa77d031838db923dec29024df638520586c925 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Sun, 19 Feb 2012 19:26:30 -0500 Subject: show: Further general simplifications of the JSON formatter --- notmuch-show.c | 61 +++++++++++++++++++++------------------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 07276c7..6a171a4 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -678,9 +678,10 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) GMimeObject *meta = node->envelope_part ? GMIME_OBJECT (node->envelope_part) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); - GMimeStream *stream_memory = g_mime_stream_mem_new (); const char *cid = g_mime_object_get_content_id (meta); - GByteArray *part_content; + const char *filename = GMIME_IS_PART (node->part) ? + g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; + const char *terminator = ""; int i; if (!first) @@ -688,15 +689,9 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("{\"id\": %d", node->part_num); - if (node->decrypt_attempted) { - printf (", \"encstatus\": [{\"status\": "); - if (node->decrypt_success) { - printf ("\"good\""); - } else { - printf ("\"bad\""); - } - printf ("}]"); - } + if (node->decrypt_attempted) + printf (", \"encstatus\": [{\"status\": \"%s\"}]", + node->decrypt_success ? "good" : "bad"); if (node->verify_attempted) { printf (", \"sigstatus\": "); @@ -706,16 +701,13 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf (", \"content-type\": %s", json_quote_str (local, g_mime_content_type_to_string (content_type))); - if (cid != NULL) - printf(", \"content-id\": %s", json_quote_str (local, cid)); + if (cid) + printf (", \"content-id\": %s", json_quote_str (local, cid)); - if (GMIME_IS_PART (node->part)) { - const char *filename = g_mime_part_get_filename (GMIME_PART (node->part)); - if (filename) - printf (", \"filename\": %s", json_quote_str (local, filename)); - } + if (filename) + printf (", \"filename\": %s", json_quote_str (local, filename)); - if (g_mime_content_type_is_type (content_type, "text", "*")) { + if (GMIME_IS_PART (node->part)) { /* For non-HTML text parts, we include the content in the * JSON. Since JSON must be Unicode, we handle charset * decoding here and do not report a charset to the caller. @@ -730,42 +722,33 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) if (content_charset != NULL) printf (", \"content-charset\": %s", json_quote_str (local, content_charset)); - } else { + } else if (g_mime_content_type_is_type (content_type, "text", "*")) { + GMimeStream *stream_memory = g_mime_stream_mem_new (); + GByteArray *part_content; show_text_part_content (node->part, stream_memory); part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory)); printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len)); + g_object_unref (stream_memory); } - } else if (g_mime_content_type_is_type (content_type, "multipart", "*")) { + } else if (GMIME_IS_MULTIPART (node->part)) { printf (", \"content\": ["); - } else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) { + terminator = "]"; + } else if (GMIME_IS_MESSAGE (node->part)) { printf (", \"content\": [{"); - } - - if (stream_memory) - g_object_unref (stream_memory); - - if (GMIME_IS_MESSAGE (node->part)) { printf ("\"headers\": "); format_headers_json (local, GMIME_MESSAGE (node->part)); printf (", \"body\": ["); + terminator = "]}]"; } + talloc_free (local); + for (i = 0; i < node->nchildren; i++) format_part_json (ctx, mime_node_child (node, i), i == 0); - if (GMIME_IS_MESSAGE (node->part)) - printf ("]"); - - if (g_mime_content_type_is_type (content_type, "multipart", "*")) - printf ("]"); - else if (g_mime_content_type_is_type (content_type, "message", "rfc822")) - printf ("}]"); - - printf ("}"); - - talloc_free (local); + printf ("%s}", terminator); } static void -- cgit v1.2.3 From 5234f808bc5fe5c4ce28c04f9add38cc5bd1fb3d Mon Sep 17 00:00:00 2001 From: Pieter Praet Date: Thu, 1 Mar 2012 22:44:30 +0100 Subject: NEWS: add entry for `notmuch-show-stash-mlarchive-link{,-and-go}' --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 8fb2f24..a739914 100644 --- a/NEWS +++ b/NEWS @@ -80,6 +80,12 @@ Attachment buttons can be used to view or save attachments. button 1 saves the attachment but this is customisable (option Notmuch Show Part Button Default Action). +New functions + + `notmuch-show-stash-mlarchive-link{,-and-go}' allow stashing and + optionally visiting a URI to the current message at one of a number + of Mailing List Archives. + Library changes --------------- -- cgit v1.2.3 From 1fb5c77818ff00714a7012b74b2bc620818f4a36 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Thu, 1 Mar 2012 15:34:05 +0200 Subject: emacs: retain show buffer state after retrieving gpg key After retrieving gpg key retain show buffer state like in all other operations (i.e. no other calls to notmuch-show-refresh-view provides optional reset-state argument). Emacs MUA keeps current message under cursor instead of going first open message(possibly marking it read). --- emacs/notmuch-crypto.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index 94da325..e916d66 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -145,7 +145,7 @@ mode." (insert "\n") (call-process "gpg" nil t t "--list-keys" keyid)) (recenter -1)) - (notmuch-show-refresh-view t))) + (notmuch-show-refresh-view))) (defun notmuch-crypto-insert-encstatus-button (encstatus) (let* ((status (plist-get encstatus :status)) -- cgit v1.2.3 From 2c077b1e4a3202fb3b3269a3f734e3ae75ddceff Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Thu, 1 Mar 2012 15:34:06 +0200 Subject: Spelling fix Retreive is spelled as r e t r i e v e . --- emacs/notmuch-crypto.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el index e916d66..83e5d37 100644 --- a/emacs/notmuch-crypto.el +++ b/emacs/notmuch-crypto.el @@ -101,7 +101,7 @@ mode." (let ((keyid (concat "0x" (plist-get sigstatus :keyid)))) (setq label (concat "Unknown key ID " keyid " or unsupported algorithm")) (setq button-action 'notmuch-crypto-sigstatus-error-callback) - (setq help-msg (concat "Click to retreive key ID " keyid " from keyserver and redisplay.")))) + (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver and redisplay.")))) ((string= status "bad") (let ((keyid (concat "0x" (plist-get sigstatus :keyid)))) (setq label (concat "Bad signature (claimed key ID " keyid ")")) -- cgit v1.2.3 From d845662970c7627a7c6d03d4698119c2263c586f Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:33 +0000 Subject: cli: add --no-exclude option to count and search. This option turns off the exclusion so all matching messages are returned. We do not need to add this to notmuch-show as that does not (yet) exclude. --- notmuch-count.c | 17 +++++++++++------ notmuch-search.c | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/notmuch-count.c b/notmuch-count.c index 63459fb..5364507 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -35,8 +35,7 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) char *query_str; int opt_index; int output = OUTPUT_MESSAGES; - const char **search_exclude_tags; - size_t search_exclude_tags_length; + notmuch_bool_t no_exclude = FALSE; unsigned int i; notmuch_opt_desc_t options[] = { @@ -44,6 +43,7 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS }, { "messages", OUTPUT_MESSAGES }, { 0, 0 } } }, + { NOTMUCH_OPT_BOOLEAN, &no_exclude, "no-exclude", 'd', 0 }, { 0, 0, 0, 0, 0 } }; @@ -78,10 +78,15 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) return 1; } - search_exclude_tags = notmuch_config_get_search_exclude_tags - (config, &search_exclude_tags_length); - for (i = 0; i < search_exclude_tags_length; i++) - notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + if (!no_exclude) { + const char **search_exclude_tags; + size_t search_exclude_tags_length; + + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + } switch (output) { case OUTPUT_MESSAGES: diff --git a/notmuch-search.c b/notmuch-search.c index 92ce38a..6d6c0e6 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -426,8 +426,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) output_t output = OUTPUT_SUMMARY; int offset = 0; int limit = -1; /* unlimited */ - const char **search_exclude_tags; - size_t search_exclude_tags_length; + notmuch_bool_t no_exclude = FALSE; unsigned int i; enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT } @@ -449,6 +448,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) { "files", OUTPUT_FILES }, { "tags", OUTPUT_TAGS }, { 0, 0 } } }, + { NOTMUCH_OPT_BOOLEAN, &no_exclude, "no-exclude", 'd', 0 }, { NOTMUCH_OPT_INT, &offset, "offset", 'O', 0 }, { NOTMUCH_OPT_INT, &limit, "limit", 'L', 0 }, { 0, 0, 0, 0, 0 } @@ -496,10 +496,15 @@ notmuch_search_command (void *ctx, int argc, char *argv[]) notmuch_query_set_sort (query, sort); - search_exclude_tags = notmuch_config_get_search_exclude_tags - (config, &search_exclude_tags_length); - for (i = 0; i < search_exclude_tags_length; i++) - notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + if (!no_exclude) { + const char **search_exclude_tags; + size_t search_exclude_tags_length; + + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + } switch (output) { default: -- cgit v1.2.3 From 4eab10f0f82cbae8635552a05a7d0ee4ca1ef8cc Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:34 +0000 Subject: cli: Add --no-exclude to the man pages for search and count --- man/man1/notmuch-count.1 | 7 +++++++ man/man1/notmuch-search.1 | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/man/man1/notmuch-count.1 b/man/man1/notmuch-count.1 index 0d0ab5d..805a8ae 100644 --- a/man/man1/notmuch-count.1 +++ b/man/man1/notmuch-count.1 @@ -38,6 +38,13 @@ Output the number of matching messages. This is the default. Output the number of matching threads. .RE .RE + +.RS 4 +.TP 4 +.BR \-\-no\-exclude + +Do not exclude the messages matching search.exclude_tags in the config file. +.RE .RE .RE diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1 index 19d85df..8426aa3 100644 --- a/man/man1/notmuch-search.1 +++ b/man/man1/notmuch-search.1 @@ -112,6 +112,13 @@ result from the end. Limit the number of displayed results to N. .RE +.RS 4 +.TP 4 +.BR \-\-no\-exclude + +Do not exclude the messages matching search.exclude_tags in the config file. +.RE + .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -- cgit v1.2.3 From 98f5b0f09a816dfe5bc206f8b76c87223437e63d Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:35 +0000 Subject: test: add tests for new cli --no-exclude option The tests test the new --no-exclude option to search and count. There were no existing tests for the exclude behaviour for count so added these too. --- test/count | 21 +++++++++++++++++++++ test/search | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/test/count b/test/count index 300b171..976fff1 100755 --- a/test/count +++ b/test/count @@ -37,4 +37,25 @@ test_expect_equal \ "0" \ "`notmuch count --output=threads ${SEARCH}`" +test_begin_subtest "count excluding \"deleted\" messages" +notmuch config set search.exclude_tags = deleted +generate_message '[subject]="Not deleted"' +generate_message '[subject]="Another not deleted"' +generate_message '[subject]="Deleted"' +notmuch new > /dev/null +notmuch tag +deleted id:$gen_msg_id +test_expect_equal \ + "2" \ + "`notmuch count subject:deleted`" + +test_begin_subtest "count \"deleted\" messages, exclude overridden" +test_expect_equal \ + "1" \ + "`notmuch count subject:deleted and tag:deleted`" + +test_begin_subtest "count \"deleted\" messages, with --no-exclude" +test_expect_equal \ + "3" \ + "`notmuch count --no-exclude subject:deleted`" + test_done diff --git a/test/search b/test/search index 414be35..3da5d17 100755 --- a/test/search +++ b/test/search @@ -148,6 +148,11 @@ output=$(notmuch search subject:deleted | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)" +test_begin_subtest "Don't exclude \"deleted\" messages when --no-exclude specified" +output=$(notmuch search --no-exclude subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) +thread:XXX 2001-01-05 [2/2] Notmuch Test Suite; Deleted (deleted inbox unread)" + test_begin_subtest "Don't exclude \"deleted\" messages from search if not configured" notmuch config set search.exclude_tags output=$(notmuch search subject:deleted | notmuch_search_sanitize) -- cgit v1.2.3 From 08f7b026a9f8a32fbe14eb73b99a026544b22900 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:36 +0000 Subject: lib: Rearrange the exclude code in query.cc Slightly refactor the exclude code to give the callers access to the exclude query itself. There should be no functional change. --- lib/query.cc | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/query.cc b/lib/query.cc index 0b36602..c25b301 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -122,12 +122,15 @@ _notmuch_messages_destructor (notmuch_mset_messages_t *messages) return 0; } -/* Return a query that does not match messages with the excluded tags - * registered with the query. Any tags that explicitly appear in - * xquery will not be excluded. */ +/* Return a query that matches messages with the excluded tags + * registered with query. Any tags that explicitly appear in xquery + * will not be excluded. The caller of this function has to combine + * the returned query appropriately.*/ static Xapian::Query _notmuch_exclude_tags (notmuch_query_t *query, Xapian::Query xquery) { + Xapian::Query exclude_query = Xapian::Query::MatchNothing; + for (notmuch_string_node_t *term = query->exclude_terms->head; term; term = term->next) { Xapian::TermIterator it = xquery.get_terms_begin (); @@ -137,10 +140,10 @@ _notmuch_exclude_tags (notmuch_query_t *query, Xapian::Query xquery) break; } if (it == end) - xquery = Xapian::Query (Xapian::Query::OP_AND_NOT, - xquery, Xapian::Query (term->string)); + exclude_query = Xapian::Query (Xapian::Query::OP_OR, + exclude_query, Xapian::Query (term->string)); } - return xquery; + return exclude_query; } notmuch_messages_t * @@ -168,7 +171,7 @@ notmuch_query_search_messages (notmuch_query_t *query) Xapian::Query mail_query (talloc_asprintf (query, "%s%s", _find_prefix ("type"), "mail")); - Xapian::Query string_query, final_query; + Xapian::Query string_query, final_query, exclude_query; Xapian::MSet mset; unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_PHRASE | @@ -188,7 +191,10 @@ notmuch_query_search_messages (notmuch_query_t *query) mail_query, string_query); } - final_query = _notmuch_exclude_tags (query, final_query); + exclude_query = _notmuch_exclude_tags (query, final_query); + + final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, + final_query, exclude_query); enquire.set_weighting_scheme (Xapian::BoolWeight()); @@ -449,7 +455,7 @@ notmuch_query_count_messages (notmuch_query_t *query) Xapian::Query mail_query (talloc_asprintf (query, "%s%s", _find_prefix ("type"), "mail")); - Xapian::Query string_query, final_query; + Xapian::Query string_query, final_query, exclude_query; Xapian::MSet mset; unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_PHRASE | @@ -469,7 +475,10 @@ notmuch_query_count_messages (notmuch_query_t *query) mail_query, string_query); } - final_query = _notmuch_exclude_tags (query, final_query); + exclude_query = _notmuch_exclude_tags (query, final_query); + + final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, + final_query, exclude_query); enquire.set_weighting_scheme(Xapian::BoolWeight()); enquire.set_docid_order(Xapian::Enquire::ASCENDING); -- cgit v1.2.3 From c9eb94d7fb520612374870dda9b9058a85c9b03d Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:37 +0000 Subject: lib: Make notmuch_query_search_messages set the exclude flag Add a flag NOTMUCH_MESSAGE_FLAG_EXCLUDED which is set by notmuch_query_search_messages for excluded messages. Also add an option omit_excluded_messages to the search that we do not want the excludes at all. This exclude flag will be added to notmuch_query_search threads in the next patch. --- lib/notmuch-private.h | 1 + lib/notmuch.h | 10 ++++++++- lib/query.cc | 59 +++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 7bf153e..e791bb0 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -401,6 +401,7 @@ typedef struct _notmuch_message_list { */ struct visible _notmuch_messages { notmuch_bool_t is_of_list_type; + notmuch_doc_id_set_t *excluded_doc_ids; notmuch_message_node_t *iterator; }; diff --git a/lib/notmuch.h b/lib/notmuch.h index 7929fe7..f75afae 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -449,6 +449,13 @@ typedef enum { const char * notmuch_query_get_query_string (notmuch_query_t *query); +/* Specify whether to results should omit the excluded results rather + * than just marking them excluded. This is useful for passing a + * notmuch_messages_t not containing the excluded messages to other + * functions. */ +void +notmuch_query_set_omit_excluded_messages (notmuch_query_t *query, notmuch_bool_t omit); + /* Specify the sorting desired for this query. */ void notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort); @@ -895,7 +902,8 @@ notmuch_message_get_filenames (notmuch_message_t *message); /* Message flags */ typedef enum _notmuch_message_flag { - NOTMUCH_MESSAGE_FLAG_MATCH + NOTMUCH_MESSAGE_FLAG_MATCH, + NOTMUCH_MESSAGE_FLAG_EXCLUDED } notmuch_message_flag_t; /* Get a value of a flag for the email corresponding to 'message'. */ diff --git a/lib/query.cc b/lib/query.cc index c25b301..ef2a11f 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -28,6 +28,7 @@ struct _notmuch_query { const char *query_string; notmuch_sort_t sort; notmuch_string_list_t *exclude_terms; + notmuch_bool_t omit_excluded_messages; }; typedef struct _notmuch_mset_messages { @@ -57,6 +58,12 @@ struct visible _notmuch_threads { notmuch_doc_id_set_t match_set; }; +/* We need this in the message functions so forward declare. */ +static notmuch_bool_t +_notmuch_doc_id_set_init (void *ctx, + notmuch_doc_id_set_t *doc_ids, + GArray *arr); + notmuch_query_t * notmuch_query_create (notmuch_database_t *notmuch, const char *query_string) @@ -79,6 +86,8 @@ notmuch_query_create (notmuch_database_t *notmuch, query->exclude_terms = _notmuch_string_list_create (query); + query->omit_excluded_messages = FALSE; + return query; } @@ -88,6 +97,12 @@ notmuch_query_get_query_string (notmuch_query_t *query) return query->query_string; } +void +notmuch_query_set_omit_excluded_messages (notmuch_query_t *query, notmuch_bool_t omit) +{ + query->omit_excluded_messages = omit; +} + void notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort) { @@ -124,8 +139,9 @@ _notmuch_messages_destructor (notmuch_mset_messages_t *messages) /* Return a query that matches messages with the excluded tags * registered with query. Any tags that explicitly appear in xquery - * will not be excluded. The caller of this function has to combine - * the returned query appropriately.*/ + * will not be excluded, and will be removed from the list of exclude + * tags. The caller of this function has to combine the returned + * query appropriately.*/ static Xapian::Query _notmuch_exclude_tags (notmuch_query_t *query, Xapian::Query xquery) { @@ -142,6 +158,8 @@ _notmuch_exclude_tags (notmuch_query_t *query, Xapian::Query xquery) if (it == end) exclude_query = Xapian::Query (Xapian::Query::OP_OR, exclude_query, Xapian::Query (term->string)); + else + term->string = talloc_strdup (query, ""); } return exclude_query; } @@ -173,6 +191,7 @@ notmuch_query_search_messages (notmuch_query_t *query) "mail")); Xapian::Query string_query, final_query, exclude_query; Xapian::MSet mset; + Xapian::MSetIterator iterator; unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_PHRASE | Xapian::QueryParser::FLAG_LOVEHATE | @@ -190,11 +209,35 @@ notmuch_query_search_messages (notmuch_query_t *query) final_query = Xapian::Query (Xapian::Query::OP_AND, mail_query, string_query); } + messages->base.excluded_doc_ids = NULL; + + if (query->exclude_terms) { + exclude_query = _notmuch_exclude_tags (query, final_query); + exclude_query = Xapian::Query (Xapian::Query::OP_AND, + exclude_query, final_query); + + if (query->omit_excluded_messages) + final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, + final_query, exclude_query); + else { + enquire.set_weighting_scheme (Xapian::BoolWeight()); + enquire.set_query (exclude_query); + + mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ()); + + GArray *excluded_doc_ids = g_array_new (FALSE, FALSE, sizeof (unsigned int)); + + for (iterator = mset.begin (); iterator != mset.end (); iterator++) { + unsigned int doc_id = *iterator; + g_array_append_val (excluded_doc_ids, doc_id); + } + messages->base.excluded_doc_ids = talloc (messages, _notmuch_doc_id_set); + _notmuch_doc_id_set_init (query, messages->base.excluded_doc_ids, + excluded_doc_ids); + g_array_unref (excluded_doc_ids); + } + } - exclude_query = _notmuch_exclude_tags (query, final_query); - - final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, - final_query, exclude_query); enquire.set_weighting_scheme (Xapian::BoolWeight()); @@ -283,6 +326,10 @@ _notmuch_mset_messages_get (notmuch_messages_t *messages) INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n"); } + if (messages->excluded_doc_ids && + _notmuch_doc_id_set_contains (messages->excluded_doc_ids, doc_id)) + notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, TRUE); + return message; } -- cgit v1.2.3 From 1a53f9f116fa7c460cda3df532be921baaafb082 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:38 +0000 Subject: lib: Add the exclude flag to notmuch_query_search_threads Add the NOTMUCH_MESSAGE_FLAG_EXCLUDED flag to notmuch_query_search_threads. Implemented by inspecting the tags directly in _notmuch_thread_create/_thread_add_message rather than as a Xapian query for speed reasons. Note notmuch_thread_get_matched_messages now returns the number of non-excluded matching messages. This API is not totally desirable but fixing it means breaking binary compatibility so we delay that. --- lib/notmuch-private.h | 7 +++++-- lib/notmuch.h | 6 ++++-- lib/query.cc | 1 + lib/thread.cc | 18 +++++++++++++++--- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index e791bb0..ea836f7 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -148,6 +148,8 @@ typedef enum _notmuch_private_status { typedef struct _notmuch_doc_id_set notmuch_doc_id_set_t; +typedef struct _notmuch_string_list notmuch_string_list_t; + /* database.cc */ /* Lookup a prefix value by name. @@ -216,6 +218,7 @@ _notmuch_thread_create (void *ctx, notmuch_database_t *notmuch, unsigned int seed_doc_id, notmuch_doc_id_set_t *match_set, + notmuch_string_list_t *excluded_terms, notmuch_sort_t sort); /* message.cc */ @@ -459,11 +462,11 @@ typedef struct _notmuch_string_node { struct _notmuch_string_node *next; } notmuch_string_node_t; -typedef struct visible _notmuch_string_list { +struct visible _notmuch_string_list { int length; notmuch_string_node_t *head; notmuch_string_node_t **tail; -} notmuch_string_list_t; +}; notmuch_string_list_t * _notmuch_string_list_create (const void *ctx); diff --git a/lib/notmuch.h b/lib/notmuch.h index f75afae..babd208 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -672,8 +672,10 @@ notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread); /* Get the number of messages in 'thread' that matched the search. * * This count includes only the messages in this thread that were - * matched by the search from which the thread was created. Contrast - * with notmuch_thread_get_total_messages() . + * matched by the search from which the thread was created and were + * not excluded by any exclude tags passed in with the query (see + * notmuch_query_add_tag_exclude). Contrast with + * notmuch_thread_get_total_messages() . */ int notmuch_thread_get_matched_messages (notmuch_thread_t *thread); diff --git a/lib/query.cc b/lib/query.cc index ef2a11f..ab18fbc 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -475,6 +475,7 @@ notmuch_threads_get (notmuch_threads_t *threads) threads->query->notmuch, doc_id, &threads->match_set, + threads->query->exclude_terms, threads->query->sort); } diff --git a/lib/thread.cc b/lib/thread.cc index 0435ee6..e976d64 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -214,7 +214,8 @@ _thread_cleanup_author (notmuch_thread_t *thread, */ static void _thread_add_message (notmuch_thread_t *thread, - notmuch_message_t *message) + notmuch_message_t *message, + notmuch_string_list_t *exclude_terms) { notmuch_tags_t *tags; const char *tag; @@ -262,6 +263,15 @@ _thread_add_message (notmuch_thread_t *thread, notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); + /* Mark excluded messages. */ + for (notmuch_string_node_t *term = exclude_terms->head; term; + term = term->next) { + /* We ignore initial 'K'. */ + if (strcmp(tag, (term->string + 1)) == 0) { + notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, TRUE); + break; + } + } g_hash_table_insert (thread->tags, xstrdup (tag), NULL); } } @@ -321,7 +331,8 @@ _thread_add_matched_message (notmuch_thread_t *thread, _thread_set_subject_from_message (thread, message); } - thread->matched_messages++; + if (!notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)) + thread->matched_messages++; if (g_hash_table_lookup_extended (thread->message_hash, notmuch_message_get_message_id (message), NULL, @@ -392,6 +403,7 @@ _notmuch_thread_create (void *ctx, notmuch_database_t *notmuch, unsigned int seed_doc_id, notmuch_doc_id_set_t *match_set, + notmuch_string_list_t *exclude_terms, notmuch_sort_t sort) { notmuch_thread_t *thread; @@ -467,7 +479,7 @@ _notmuch_thread_create (void *ctx, if (doc_id == seed_doc_id) message = seed_message; - _thread_add_message (thread, message); + _thread_add_message (thread, message, exclude_terms); if ( _notmuch_doc_id_set_contains (match_set, doc_id)) { _notmuch_doc_id_set_remove (match_set, doc_id); -- cgit v1.2.3 From c440e597f9a32c7050fa0cc553b6166941724588 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:39 +0000 Subject: test: update search test to reflect exclude flag notmuch-search.c now returns all matching threads even if it the match is a search.tag_excluded message (but with a mark indicating this). Update the test to reflect this. --- test/search | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/search b/test/search index 3da5d17..081f60c 100755 --- a/test/search +++ b/test/search @@ -136,7 +136,8 @@ generate_message '[subject]="Deleted"' notmuch new > /dev/null notmuch tag +deleted id:$gen_msg_id output=$(notmuch search subject:deleted | notmuch_search_sanitize) -test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)" +test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) +thread:XXX 2001-01-05 [0/1] Notmuch Test Suite; Deleted (deleted inbox unread)" test_begin_subtest "Exclude \"deleted\" messages from search, overridden" output=$(notmuch search subject:deleted and tag:deleted | notmuch_search_sanitize) -- cgit v1.2.3 From ebe5e6712a9386071ee73195214ec4cb788a38b2 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:40 +0000 Subject: cli: Make notmuch-show respect excludes. This adds the excludes to notmuch-show.c. We do not exclude when only a single message (or part) is requested. notmuch-show will output the exclude information when either text or json format is requested. As this changes the output from notmuch-show it breaks many tests (in a trivial and expected fashion). --- notmuch-show.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 6a171a4..8c0b925 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -143,9 +143,10 @@ format_message_json (const void *ctx, notmuch_message_t *message) date = notmuch_message_get_date (message); relative_date = notmuch_time_relative_date (ctx, date); - printf ("\"id\": %s, \"match\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [", + printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [", json_quote_str (ctx_quote, notmuch_message_get_message_id (message)), notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false", + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false", json_quote_str (ctx_quote, notmuch_message_get_filename (message)), date, relative_date); @@ -579,11 +580,12 @@ format_part_text (const void *ctx, mime_node_t *node, notmuch_message_t *message = node->envelope_file; part_type = "message"; - printf ("\f%s{ id:%s depth:%d match:%d filename:%s\n", + printf ("\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n", part_type, notmuch_message_get_message_id (message), indent, - notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0, + notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0, notmuch_message_get_filename (message)); } else { GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); @@ -984,6 +986,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) notmuch_show_params_t params = { .part = -1 }; int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED; notmuch_bool_t verify = FALSE; + notmuch_bool_t no_exclude = FALSE; notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f', @@ -996,6 +999,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 }, { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 }, { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 }, + { NOTMUCH_OPT_BOOLEAN, &no_exclude, "no-exclude", 'n', 0 }, { 0, 0, 0, 0, 0 } }; @@ -1083,10 +1087,23 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) ret = do_show_single (ctx, query, format, ¶ms); - else + else { + if (!no_exclude) { + const char **search_exclude_tags; + size_t search_exclude_tags_length; + unsigned int i; + + search_exclude_tags = notmuch_config_get_search_exclude_tags + (config, &search_exclude_tags_length); + for (i = 0; i < search_exclude_tags_length; i++) + notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); + } ret = do_show (ctx, query, format, ¶ms); + } + notmuch_query_destroy (query); notmuch_database_close (notmuch); -- cgit v1.2.3 From c8cf9e92d80f960bb8231b518c6967a7aab7f260 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:41 +0000 Subject: test: update tests to reflect the exclude flag notmuch show outputs the exclude flag so many tests using notmuch show failed. This commit adds "excluded:0" or "excluded: false" to the expected outputs. After this commit there should be no failing tests. --- test/crypto | 9 ++++++++- test/encoding | 2 +- test/json | 6 +++--- test/maildir-sync | 1 + test/multipart | 6 +++--- test/thread-naming | 16 ++++++++-------- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/test/crypto b/test/crypto index 7e774c8..4de4d2b 100755 --- a/test/crypto +++ b/test/crypto @@ -43,6 +43,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \ | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", @@ -76,6 +77,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \ | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", @@ -111,6 +113,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \ | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", @@ -150,7 +153,7 @@ test_begin_subtest "decryption, --format=text" output=$(notmuch show --format=text --decrypt subject:"test encrypted message 001" \ | notmuch_show_sanitize_all \ | sed -e 's|"created": [1234567890]*|"created": 946728000|') -expected=' message{ id:XXXXX depth:0 match:1 filename:XXXXX +expected=' message{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX header{ Notmuch Test Suite (2000-01-01) (encrypted inbox) Subject: test encrypted message 001 @@ -184,6 +187,7 @@ output=$(notmuch show --format=json --decrypt subject:"test encrypted message 00 | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", @@ -238,6 +242,7 @@ output=$(notmuch show --format=json --decrypt subject:"test encrypted message 00 | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", @@ -272,6 +277,7 @@ output=$(notmuch show --format=json --decrypt subject:"test encrypted message 00 | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", @@ -326,6 +332,7 @@ output=$(notmuch show --format=json --verify subject:"test signed message 001" \ | sed -e 's|"created": [1234567890]*|"created": 946728000|') expected='[[[{"id": "XXXXX", "match": true, + "excluded": false, "filename": "YYYYY", "timestamp": 946728000, "date_relative": "2000-01-01", diff --git a/test/encoding b/test/encoding index f0d073c..98abf77 100755 --- a/test/encoding +++ b/test/encoding @@ -6,7 +6,7 @@ test_begin_subtest "Message with text of unknown charset" add_message '[content-type]="text/plain; charset=unknown-8bit"' \ "[body]=irrelevant" output=$(notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize) -test_expect_equal "$output" " message{ id:msg-001@notmuch-test-suite depth:0 match:1 filename:/XXX/mail/msg-001 +test_expect_equal "$output" " message{ id:msg-001@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-001 header{ Notmuch Test Suite (2001-01-05) (inbox unread) Subject: Test message #1 diff --git a/test/json b/test/json index 1bdffd2..6439788 100755 --- a/test/json +++ b/test/json @@ -5,7 +5,7 @@ test_description="--format=json output" test_begin_subtest "Show message: json" add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-show-message\"" output=$(notmuch show --format=json "json-show-message") -test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"json-show-message\n\"}]}, []]]]" test_begin_subtest "Search message: json" add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-search-message\"" @@ -22,7 +22,7 @@ test_expect_equal "$output" "[{\"thread\": \"XXX\", test_begin_subtest "Show message: json, utf-8" add_message "[subject]=\"json-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\"" output=$(notmuch show --format=json "jsön-show-méssage") -test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"${gen_msg_id}\", \"match\": true, \"excluded\": false, \"filename\": \"${gen_msg_filename}\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\",\"unread\"], \"headers\": {\"Subject\": \"json-show-utf8-body-sübjéct\", \"From\": \"Notmuch Test Suite \", \"To\": \"Notmuch Test Suite \", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"text/plain\", \"content\": \"jsön-show-méssage\n\"}]}, []]]]" test_begin_subtest "Show message: json, inline attachment filename" subject='json-show-inline-attachment-filename' @@ -35,7 +35,7 @@ emacs_deliver_message \ (insert \"Message-ID: <$id>\n\")" output=$(notmuch show --format=json "id:$id") filename=$(notmuch search --output=files "id:$id") -test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]" +test_expect_equal "$output" "[[[{\"id\": \"$id\", \"match\": true, \"excluded\": false, \"filename\": \"$filename\", \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite \", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"filename\": \"README\"}]}]}, []]]]" test_begin_subtest "Search message: json, utf-8" add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\"" diff --git a/test/maildir-sync b/test/maildir-sync index 1ee2db0..d72ec07 100755 --- a/test/maildir-sync +++ b/test/maildir-sync @@ -46,6 +46,7 @@ test_begin_subtest "notmuch show works with renamed file (without notmuch new)" output=$(notmuch show --format=json id:${gen_msg_id} | filter_show_json) test_expect_equal "$output" '[[[{"id": "adding-replied-tag@notmuch-test-suite", "match": true, +"excluded": false, "filename": "MAIL_DIR/cur/adding-replied-tag:2,RS", "timestamp": 978709437, "date_relative": "2001-01-05", diff --git a/test/multipart b/test/multipart index a3036b4..53782c6 100755 --- a/test/multipart +++ b/test/multipart @@ -108,7 +108,7 @@ notmuch new > /dev/null test_begin_subtest "--format=text --part=0, full message" notmuch show --format=text --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED - message{ id:87liy5ap00.fsf@yoom.home.cworth.org depth:0 match:1 filename:${MAIL_DIR}/multipart + message{ id:87liy5ap00.fsf@yoom.home.cworth.org depth:0 match:1 excluded:0 filename:${MAIL_DIR}/multipart header{ Carl Worth (2001-01-05) (attachment inbox signed unread) Subject: Multipart message @@ -322,7 +322,7 @@ notmuch show --format=json --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' | s echo >>OUTPUT # expect *no* newline at end of output cat <EXPECTED -{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ +{"id": "87liy5ap00.fsf@yoom.home.cworth.org", "match": true, "excluded": false, "filename": "${MAIL_DIR}/multipart", "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["attachment","inbox","signed","unread"], "headers": {"Subject": "Multipart message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [ {"id": 1, "content-type": "multipart/signed", "content": [ {"id": 2, "content-type": "multipart/mixed", "content": [ {"id": 3, "content-type": "message/rfc822", "content": [{"headers": {"Subject": "html message", "From": "Carl Worth ", "To": "cworth@cworth.org", "Date": "Fri, 05 Jan 2001 15:42:57 +0000"}, "body": [ @@ -594,4 +594,4 @@ notmuch show --format=raw --part=3 id:base64-part-with-crlf > crlf.out echo -n -e "\xEF\x0D\x0A" > crlf.expected test_expect_equal_file crlf.out crlf.expected -test_done +test_done \ No newline at end of file diff --git a/test/thread-naming b/test/thread-naming index 942e593..1a1a48f 100755 --- a/test/thread-naming +++ b/test/thread-naming @@ -65,7 +65,7 @@ test_expect_equal "$output" "thread:XXX 2001-01-12 [6/8] Notmuch Test Suite; t test_begin_subtest 'Test order of messages in "notmuch show"' output=$(notmuch show thread-naming | notmuch_show_sanitize) -test_expect_equal "$output" " message{ id:msg-$(printf "%03d" $first)@notmuch-test-suite depth:0 match:1 filename:/XXX/mail/msg-$(printf "%03d" $first) +test_expect_equal "$output" " message{ id:msg-$(printf "%03d" $first)@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $first) header{ Notmuch Test Suite (2001-01-05) (unread) Subject: thread-naming: Initial thread subject @@ -79,7 +79,7 @@ This is just a test message (#$first) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 1)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 1))) + message{ id:msg-$(printf "%03d" $((first + 1)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 1))) header{ Notmuch Test Suite (2001-01-06) (inbox unread) Subject: thread-naming: Older changed subject @@ -93,7 +93,7 @@ This is just a test message (#$((first + 1))) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 2)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 2))) + message{ id:msg-$(printf "%03d" $((first + 2)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 2))) header{ Notmuch Test Suite (2001-01-07) (inbox unread) Subject: thread-naming: Newer changed subject @@ -107,7 +107,7 @@ This is just a test message (#$((first + 2))) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 3)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 3))) + message{ id:msg-$(printf "%03d" $((first + 3)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 3))) header{ Notmuch Test Suite (2001-01-08) (unread) Subject: thread-naming: Final thread subject @@ -121,7 +121,7 @@ This is just a test message (#$((first + 3))) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 4)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 4))) + message{ id:msg-$(printf "%03d" $((first + 4)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 4))) header{ Notmuch Test Suite (2001-01-09) (inbox unread) Subject: Re: thread-naming: Initial thread subject @@ -135,7 +135,7 @@ This is just a test message (#$((first + 4))) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 5)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 5))) + message{ id:msg-$(printf "%03d" $((first + 5)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 5))) header{ Notmuch Test Suite (2001-01-10) (inbox unread) Subject: Aw: thread-naming: Initial thread subject @@ -149,7 +149,7 @@ This is just a test message (#$((first + 5))) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 6)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 6))) + message{ id:msg-$(printf "%03d" $((first + 6)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 6))) header{ Notmuch Test Suite (2001-01-11) (inbox unread) Subject: Vs: thread-naming: Initial thread subject @@ -163,7 +163,7 @@ This is just a test message (#$((first + 6))) part} body} message} - message{ id:msg-$(printf "%03d" $((first + 7)))@notmuch-test-suite depth:1 match:1 filename:/XXX/mail/msg-$(printf "%03d" $((first + 7))) + message{ id:msg-$(printf "%03d" $((first + 7)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 7))) header{ Notmuch Test Suite (2001-01-12) (inbox unread) Subject: Sv: thread-naming: Initial thread subject -- cgit v1.2.3 From 99c318cae464159ca5b4ec86643ccf3c8b98c1b3 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:42 +0000 Subject: man: update manpage for notmuch-show --no-exclude option --- man/man1/notmuch-show.1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index 4c5db94..d75d971 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -128,6 +128,13 @@ multipart/encrypted part will be replaced by the decrypted content. .RE +.RS 4 +.TP 4 +.B \-\-no-exclude + +Do not exclude the messages matching search.exclude_tags in the config file. +.RE + A common use of .B notmuch show is to display a single thread of email messages. For this, use a -- cgit v1.2.3 From 7a1beb9e7c7fdf8971a60d810ae24e58c2d09673 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:43 +0000 Subject: cli: omit excluded messages in results where appropriate. In all cases of notmuch count/search/show where the results returned cannot reflect the exclude flag return just the matched not-excluded results. If the caller wishes to have all the matched results (i.e., including the excluded ones) they should call with the --no-exclude option. The relevant cases are count: both threads and messages search: all cases except the summary view show: mbox format --- notmuch-count.c | 2 ++ notmuch-search.c | 9 +++++++++ notmuch-show.c | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/notmuch-count.c b/notmuch-count.c index 5364507..46b76ae 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -88,6 +88,8 @@ notmuch_count_command (void *ctx, int argc, char *argv[]) notmuch_query_add_tag_exclude (query, search_exclude_tags[i]); } + notmuch_query_set_omit_excluded_messages (query, TRUE); + switch (output) { case OUTPUT_MESSAGES: printf ("%u\n", notmuch_query_count_messages (query)); diff --git a/notmuch-search.c b/notmuch-search.c index 6d6c0e6..f6061e4 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -210,6 +210,9 @@ do_search_threads (const search_format_t *format, int first_thread = 1; int i; + if (output == OUTPUT_THREADS) + notmuch_query_set_omit_excluded_messages (query, TRUE); + if (offset < 0) { offset += notmuch_query_count_threads (query); if (offset < 0) @@ -300,6 +303,8 @@ do_search_messages (const search_format_t *format, int first_message = 1; int i; + notmuch_query_set_omit_excluded_messages (query, TRUE); + if (offset < 0) { offset += notmuch_query_count_messages (query); if (offset < 0) @@ -371,6 +376,10 @@ do_search_tags (notmuch_database_t *notmuch, const char *tag; int first_tag = 1; + notmuch_query_set_omit_excluded_messages (query, TRUE); + /* should the following only special case if no excluded terms + * specified? */ + /* Special-case query of "*" for better performance. */ if (strcmp (notmuch_query_get_query_string (query), "*") == 0) { tags = notmuch_database_get_all_tags (notmuch); diff --git a/notmuch-show.c b/notmuch-show.c index 8c0b925..05d51b2 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -1030,6 +1030,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n"); return 1; } + format = &format_mbox; break; case NOTMUCH_FORMAT_RAW: @@ -1087,6 +1088,11 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 1; } + /* if format=mbox then we can not output excluded messages as + * there is no way to make the exclude flag available */ + if (format_sel == NOTMUCH_FORMAT_MBOX) + notmuch_query_set_omit_excluded_messages (query, TRUE); + /* If a single message is requested we do not use search_excludes. */ if (params.part >= 0) ret = do_show_single (ctx, query, format, ¶ms); -- cgit v1.2.3 From 119a42571eb8a57e3f6a8ea5e44450c43dd9df04 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:44 +0000 Subject: emacs: show: recognize the exclude flag. Show mode will recognize the exclude flag by not opening excluding messages by default, and will start at the first matching non-excluded message. If there are no matching non-excluded messages it will go to the first matching (necessarily excluded) message. --- emacs/notmuch-show.el | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 7c4c0be..4a60631 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -981,7 +981,8 @@ current buffer, if possible." ;; Message visibility depends on whether it matched the search ;; criteria. - (notmuch-show-message-visible msg (plist-get msg :match)))) + (notmuch-show-message-visible msg (and (plist-get msg :match) + (not (plist-get msg :excluded)))))) (defun notmuch-show-toggle-process-crypto () "Toggle the processing of cryptographic MIME parts." @@ -1081,11 +1082,7 @@ function is used." notmuch-show-parent-buffer parent-buffer notmuch-show-query-context query-context) (notmuch-show-build-buffer) - - ;; Move to the first open message and mark it read - (if (notmuch-show-message-visible-p) - (notmuch-show-mark-read) - (notmuch-show-next-open-message)))) + (notmuch-show-goto-first-wanted-message))) (defun notmuch-show-build-buffer () (let ((inhibit-read-only t)) @@ -1167,9 +1164,7 @@ reset based on the original query." (notmuch-show-apply-state state) ;; We're resetting state, so navigate to the first open message ;; and mark it read, just like opening a new show buffer. - (if (notmuch-show-message-visible-p) - (notmuch-show-mark-read) - (notmuch-show-next-open-message))))) + (notmuch-show-goto-first-wanted-message)))) (defvar notmuch-show-stash-map (let ((map (make-sparse-keymap))) @@ -1601,6 +1596,29 @@ to show, nil otherwise." (goto-char (point-max)))) r)) +(defun notmuch-show-next-matching-message () + "Show the next matching message." + (interactive) + (let (r) + (while (and (setq r (notmuch-show-goto-message-next)) + (not (notmuch-show-get-prop :match)))) + (if r + (progn + (notmuch-show-mark-read) + (notmuch-show-message-adjust)) + (goto-char (point-max))))) + +(defun notmuch-show-goto-first-wanted-message () + "Move to the first open message and mark it read" + (goto-char (point-min)) + (if (notmuch-show-message-visible-p) + (notmuch-show-mark-read) + (notmuch-show-next-open-message)) + (when (eobp) + (goto-char (point-min)) + (unless (notmuch-show-get-prop :match) + (notmuch-show-next-matching-message)))) + (defun notmuch-show-previous-open-message () "Show the previous open message." (interactive) -- cgit v1.2.3 From 1aa4abe7a1398d19336ed436735fd1bf5eea5e06 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Thu, 1 Mar 2012 22:30:45 +0000 Subject: emacs: notmuch.el ignore excluded matches This is a small change to make notmuch.el ignore excluded matches. In the future it could do something better like add a button for rerunning the search with the excludes (particularly if nothing matches with the excludes) or having them invisible and allowing the visibility to be toggled. --- emacs/notmuch.el | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index f851c6f..99e0c93 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -872,16 +872,18 @@ non-authors is found, assume that all of the authors match." (goto-char (point-max)) (if (/= (match-beginning 1) line) (insert (concat "Error: Unexpected output from notmuch search:\n" (substring string line (match-beginning 1)) "\n"))) - (let ((beg (point))) - (notmuch-search-show-result date count authors - (notmuch-prettify-subject subject) tags) - (notmuch-search-color-line beg (point) tag-list) - (put-text-property beg (point) 'notmuch-search-thread-id thread-id) - (put-text-property beg (point) 'notmuch-search-authors authors) - (put-text-property beg (point) 'notmuch-search-subject subject) - (when (string= thread-id notmuch-search-target-thread) - (set 'found-target beg) - (set 'notmuch-search-target-thread "found"))) + ;; We currently just throw away excluded matches. + (unless (eq (aref count 1) ?0) + (let ((beg (point))) + (notmuch-search-show-result date count authors + (notmuch-prettify-subject subject) tags) + (notmuch-search-color-line beg (point) tag-list) + (put-text-property beg (point) 'notmuch-search-thread-id thread-id) + (put-text-property beg (point) 'notmuch-search-authors authors) + (put-text-property beg (point) 'notmuch-search-subject subject) + (when (string= thread-id notmuch-search-target-thread) + (set 'found-target beg) + (set 'notmuch-search-target-thread "found")))) (set 'line (match-end 0))) (set 'more nil) (while (and (< line (length string)) (= (elt string line) ?\n)) -- cgit v1.2.3 From e77b031a86ea5cbb9488592749bdc694d54fac81 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 29 Feb 2012 18:13:05 +0000 Subject: config: disable addition of exclude tags for 0.12 This disables the addition of search_exclude_tags in notmuch-setup and notmuch-config. --- notmuch-config.c | 3 +-- notmuch-setup.c | 19 +------------------ 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/notmuch-config.c b/notmuch-config.c index e9b2750..61fda3e 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -377,8 +377,7 @@ notmuch_config_open (void *ctx, if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) { if (is_new) { - const char *tags[] = { "deleted", "spam" }; - notmuch_config_set_search_exclude_tags (config, tags, 2); + /* We do not set default search_exclude_tags for 0.12 */ } else { notmuch_config_set_search_exclude_tags (config, NULL, 0); } diff --git a/notmuch-setup.c b/notmuch-setup.c index 94d0aa7..307231d 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -133,8 +133,6 @@ notmuch_setup_command (unused (void *ctx), int is_new; const char **new_tags; size_t new_tags_len; - const char **search_exclude_tags; - size_t search_exclude_tags_len; #define prompt(format, ...) \ do { \ @@ -211,22 +209,7 @@ notmuch_setup_command (unused (void *ctx), } - search_exclude_tags = notmuch_config_get_search_exclude_tags (config, &search_exclude_tags_len); - - printf ("Tags to exclude when searching messages (separated by spaces) ["); - print_tag_list (search_exclude_tags, search_exclude_tags_len); - prompt ("]: "); - - if (strlen (response)) { - GPtrArray *tags = parse_tag_list (ctx, response); - - notmuch_config_set_search_exclude_tags (config, - (const char **) tags->pdata, - tags->len); - - g_ptr_array_free (tags, TRUE); - } - + /* Temporarily remove exclude tag support for 0.12 */ if (! notmuch_config_save (config)) { if (is_new) -- cgit v1.2.3 From e83409d21c48259af0321b8c187a1980d69d1ff1 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 29 Feb 2012 18:13:06 +0000 Subject: NEWS: revert NEWS item for exclude tags for 0.12 --- NEWS | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/NEWS b/NEWS index a739914..4152e7c 100644 --- a/NEWS +++ b/NEWS @@ -11,24 +11,6 @@ Reply to sender to all. The feature is available through the new command line option --reply-to=(all|sender). -Tag exclusion - - Tags can be automatically excluded from search results by adding them - to the new 'search.exclude_tags' option in the Notmuch config file. - - This behaviour can be overridden by explicitly including an excluded - tag in your query, for example: - - notmuch search $your_query and tag:$excluded_tag - - Existing users will probably want to run "notmuch setup" again to add - the new well-commented [search] section to the configuration file. - - For new configurations, accepting the default setting will cause the - tags "deleted" and "spam" to be excluded, equivalent to running: - - notmuch config set search.exclude_tags deleted spam - Mail store folder/file ignore A new configuration option, `new.ignore`, lets users specify a -- cgit v1.2.3 From dfee0f97b92d6ca389ff81f0d1659496bb9d74f5 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 29 Feb 2012 18:13:07 +0000 Subject: man: remove search.exclude_tags from notmuch-config.1 for 0.12 --- man/man1/notmuch-config.1 | 8 -------- 1 file changed, 8 deletions(-) diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index e62577c..57eee93 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -83,14 +83,6 @@ will be ignored, regardless of the location in the mail store directory hierarchy. .RE -.RS 4 -.TP 4 -.B search.exclude_tags -A list of tags that will be excluded from search results by -default. Using an excluded tag in a query will override that -exclusion. -.RE - .RS 4 .TP 4 .B maildir.synchronize_flags -- cgit v1.2.3 From 8077cdc7bbab583d72b7ef3b761b173489a05639 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 3 Mar 2012 09:19:44 -0400 Subject: debian: add notmuch_query_add_tag_exclude to exported symbols This symbol is added in 0.12, although we are "stealthing" the feature overall. --- debian/libnotmuch2.symbols | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/libnotmuch2.symbols b/debian/libnotmuch2.symbols index 507600c..272467a 100644 --- a/debian/libnotmuch2.symbols +++ b/debian/libnotmuch2.symbols @@ -46,6 +46,7 @@ libnotmuch.so.2 libnotmuch2 #MINVER# notmuch_messages_get@Base 0.3 notmuch_messages_move_to_next@Base 0.3 notmuch_messages_valid@Base 0.3 + notmuch_query_add_tag_exclude@Base 0.12~rc1 notmuch_query_count_messages@Base 0.3 notmuch_query_count_threads@Base 0.10~rc1 notmuch_query_create@Base 0.3 -- cgit v1.2.3 From 2a1aeb2e9cd17051def308203db7739b04a19d73 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 3 Mar 2012 09:38:45 -0400 Subject: debian: update .gitignore Ignore individual binary package directories. --- debian/.gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/.gitignore b/debian/.gitignore index 801ca02..9f09f22 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -1,3 +1,10 @@ +tmp/ +libnotmuch-dev/ +libnotmuch2/ +notmuch-emacs/ +notmuch-vim/ +notmuch/ +python-notmuch/ *.debhelper *.debhelper.log *.substvars -- cgit v1.2.3 From 3ed8c3d9a6915f7598c3d2aae3c1d8ea9a9ce140 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 3 Mar 2012 09:40:18 -0400 Subject: debian: Bump standards version No actual changes are needed. --- debian/changelog | 1 + debian/control | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index e820f89..147ae36 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ notmuch (0.12~rc1-1) experimental; urgency=low * Upstream pre-release. + * Bump standards version to 3.9.3; no changes. -- David Bremner Thu, 01 Mar 2012 07:51:45 -0400 diff --git a/debian/control b/debian/control index b60790e..e1ac1a7 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,7 @@ Build-Depends: emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~), gdb, dtach (>= 0.8) -Standards-Version: 3.9.2 +Standards-Version: 3.9.3 Homepage: http://notmuchmail.org/ Vcs-Git: git://notmuchmail.org/git/notmuch Vcs-Browser: http://git.notmuchmail.org/git/notmuch -- cgit v1.2.3 From cfc5f1059aa16753cba610c41601cacc97260e08 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 2 Mar 2012 15:58:39 +0100 Subject: Actually close the xapian database in notmuch_database_close Formerly the xapian database object was deleted and closed in its destructor once the object was garbage collected. Explicitly call close() so that the database and the associated lock is released immediately. The comment is a courtesy of Austin Clements. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- lib/database.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/database.cc b/lib/database.cc index 5efa85e..8f8df1a 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -726,6 +726,17 @@ notmuch_database_close (notmuch_database_t *notmuch) } } + /* Many Xapian objects (and thus notmuch objects) hold references to + * the database, so merely deleting the database may not suffice to + * close it. Thus, we explicitly close it here. */ + if (notmuch->xapian_db != NULL) { + try { + notmuch->xapian_db->close(); + } catch (const Xapian::Error &error) { + /* do nothing */ + } + } + delete notmuch->term_gen; delete notmuch->query_parser; delete notmuch->xapian_db; -- cgit v1.2.3 From 4b3af0e4443f88f302f09a7a6099a31103d862d6 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 3 Mar 2012 08:56:15 -0400 Subject: Make exclusion visible again This reverts dfee0f9 man: remove search.exclude_tags from notmuch-config.1 for 0.12 e83409d NEWS: revert NEWS item for exclude tags for 0.12 e77b031 config: disable addition of exclude tags for 0.12 --- NEWS | 18 ++++++++++++++++++ man/man1/notmuch-config.1 | 8 ++++++++ notmuch-config.c | 3 ++- notmuch-setup.c | 19 ++++++++++++++++++- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 4152e7c..a739914 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,24 @@ Reply to sender to all. The feature is available through the new command line option --reply-to=(all|sender). +Tag exclusion + + Tags can be automatically excluded from search results by adding them + to the new 'search.exclude_tags' option in the Notmuch config file. + + This behaviour can be overridden by explicitly including an excluded + tag in your query, for example: + + notmuch search $your_query and tag:$excluded_tag + + Existing users will probably want to run "notmuch setup" again to add + the new well-commented [search] section to the configuration file. + + For new configurations, accepting the default setting will cause the + tags "deleted" and "spam" to be excluded, equivalent to running: + + notmuch config set search.exclude_tags deleted spam + Mail store folder/file ignore A new configuration option, `new.ignore`, lets users specify a diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index 57eee93..e62577c 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -83,6 +83,14 @@ will be ignored, regardless of the location in the mail store directory hierarchy. .RE +.RS 4 +.TP 4 +.B search.exclude_tags +A list of tags that will be excluded from search results by +default. Using an excluded tag in a query will override that +exclusion. +.RE + .RS 4 .TP 4 .B maildir.synchronize_flags diff --git a/notmuch-config.c b/notmuch-config.c index 61fda3e..e9b2750 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -377,7 +377,8 @@ notmuch_config_open (void *ctx, if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) { if (is_new) { - /* We do not set default search_exclude_tags for 0.12 */ + const char *tags[] = { "deleted", "spam" }; + notmuch_config_set_search_exclude_tags (config, tags, 2); } else { notmuch_config_set_search_exclude_tags (config, NULL, 0); } diff --git a/notmuch-setup.c b/notmuch-setup.c index 307231d..94d0aa7 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -133,6 +133,8 @@ notmuch_setup_command (unused (void *ctx), int is_new; const char **new_tags; size_t new_tags_len; + const char **search_exclude_tags; + size_t search_exclude_tags_len; #define prompt(format, ...) \ do { \ @@ -209,7 +211,22 @@ notmuch_setup_command (unused (void *ctx), } - /* Temporarily remove exclude tag support for 0.12 */ + search_exclude_tags = notmuch_config_get_search_exclude_tags (config, &search_exclude_tags_len); + + printf ("Tags to exclude when searching messages (separated by spaces) ["); + print_tag_list (search_exclude_tags, search_exclude_tags_len); + prompt ("]: "); + + if (strlen (response)) { + GPtrArray *tags = parse_tag_list (ctx, response); + + notmuch_config_set_search_exclude_tags (config, + (const char **) tags->pdata, + tags->len); + + g_ptr_array_free (tags, TRUE); + } + if (! notmuch_config_save (config)) { if (is_new) -- cgit v1.2.3 From 4a0740920d2a92d6a7d71ec7b8fc50e441e7552d Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Sat, 18 Feb 2012 23:12:29 +0100 Subject: emacs-hello: Do not calculate the count of the messages in hidden sections The result is that hello screen shows much faster when some sections are hidden. --- emacs/notmuch-hello.el | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el index aad373d..e9caade 100644 --- a/emacs/notmuch-hello.el +++ b/emacs/notmuch-hello.el @@ -695,16 +695,16 @@ Supports the following entries in OPTIONS as a plist: (notmuch-hello-update)) "hide")) (widget-insert "\n") - (let (target-pos - (searches (apply 'notmuch-hello-query-counts query-alist options))) - (when (and (not is-hidden) - (or (not (plist-get options :hide-if-empty)) - searches)) - (widget-insert "\n") - (setq target-pos - (notmuch-hello-insert-buttons searches)) - (indent-rigidly start (point) notmuch-hello-indent) - target-pos)))) + (let (target-pos) + (when (not is-hidden) + (let ((searches (apply 'notmuch-hello-query-counts query-alist options))) + (when (or (not (plist-get options :hide-if-empty)) + searches) + (widget-insert "\n") + (setq target-pos + (notmuch-hello-insert-buttons searches)) + (indent-rigidly start (point) notmuch-hello-indent)))) + target-pos))) (defun notmuch-hello-insert-tags-section (&optional title &rest options) "Insert a section displaying all tags with message counts. -- cgit v1.2.3 From 90f310b4fb8903898ead674059584737e448eb8a Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sun, 4 Mar 2012 10:25:38 +0200 Subject: emacs: fix MML quoting in replies The reply MML quoting added in commit ae438cc unintentionally MML quotes also the signature/encryption MML tags added via message-setup-hook, causing the reply not to be signed/encrypted. MML quote just the original message in the temp buffer before inserting it to the message buffer, to not interfere with message mode hooks or message construction in general. See [1] and [2] for bug reports. Thanks to Tim Bielawa for testing. [1] id:"87hay78x6l.fsf@wyzanski.jamesvasile.com" [2] id:"1330812262-28272-1-git-send-email-tbielawa@redhat.com". Signed-off-by: Jani Nikula --- emacs/notmuch-mua.el | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 4be7c13..13244eb 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -95,6 +95,9 @@ list." (goto-char (point-min)) (setq headers (mail-header-extract))))) (forward-line 1) + ;; Original message may contain (malicious) MML tags. We must + ;; properly quote them in the reply. + (mml-quote-region (point) (point-max)) (setq body (buffer-substring (point) (point-max)))) ;; If sender is non-nil, set the From: header to its value. (when sender @@ -116,12 +119,7 @@ list." (push-mark)) (set-buffer-modified-p nil) - (message-goto-body) - ;; Original message may contain (malicious) MML tags. We must - ;; properly quote them in the reply. Note that using `point-max' - ;; instead of `mark' here is wrong. The buffer may include user's - ;; signature which should not be MML-quoted. - (mml-quote-region (point) (mark))) + (message-goto-body)) (defun notmuch-mua-forward-message () (message-forward) -- cgit v1.2.3 From 353577ad5733b78b71d93c9bdbaae0b062d32331 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:26:57 +0000 Subject: Handle errors in mime_node_open --- mime-node.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mime-node.c b/mime-node.c index d6b4506..a95bdab 100644 --- a/mime-node.c +++ b/mime-node.c @@ -97,11 +97,26 @@ mime_node_open (const void *ctx, notmuch_message_t *message, } mctx->stream = g_mime_stream_file_new (mctx->file); + if (!mctx->stream) { + fprintf (stderr, "Out of memory.\n"); + status = NOTMUCH_STATUS_OUT_OF_MEMORY; + goto DONE; + } g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), FALSE); mctx->parser = g_mime_parser_new_with_stream (mctx->stream); + if (!mctx->parser) { + fprintf (stderr, "Out of memory.\n"); + status = NOTMUCH_STATUS_OUT_OF_MEMORY; + goto DONE; + } mctx->mime_message = g_mime_parser_construct_message (mctx->parser); + if (!mctx->mime_message) { + fprintf (stderr, "Failed to parse %s\n", filename); + status = NOTMUCH_STATUS_FILE_ERROR; + goto DONE; + } mctx->cryptoctx = cryptoctx; mctx->decrypt = decrypt; -- cgit v1.2.3 From 4d136995cebb3a339faf844fa3106f398562aad7 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Thu, 8 Mar 2012 08:45:01 -0800 Subject: Fix configure script to properly detect gmime-2.6 if available. Previously, the configure script would appear to detect gmime-2.6 if present. However, the binaries would end up being compiled against gmime-2.4. The addition of a break fixes things so that now gmime-2.6 will be used if available, falling back to gmime-2.4. --- configure | 1 + 1 file changed, 1 insertion(+) diff --git a/configure b/configure index 8b85b9d..dedb7d8 100755 --- a/configure +++ b/configure @@ -281,6 +281,7 @@ for gmimepc in gmime-2.6 gmime-2.4; do have_gmime=1 gmime_cflags=$(pkg-config --cflags $gmimepc) gmime_ldflags=$(pkg-config --libs $gmimepc) + break fi done if [ "$have_gmime" = "0" ]; then -- cgit v1.2.3 From f34613ea833196922a38cf04cd2ee766a562a410 Mon Sep 17 00:00:00 2001 From: Thomas Jost Date: Tue, 21 Feb 2012 20:35:35 +0100 Subject: build: Require gmime >= 2.6.7 gmime-2.6 had a bug [1] which made it impossible to tell why a signature verification failed when the signer key was unavailable (empty "sigstatus" field in the JSON output). Since 00b5623d the corresponding test is marked as broken when using gmime-2.6 (2.4 is fine). This bug has been fixed in gmime 2.6.5, which is now the minimal gmime-2.6 version required for building notmuch (gmime-2.4 is still available). As a consequence the version check in test/crypto can be removed. [Added by db] Although less unambigously a bug, Gmime 2.6 prior to 2.6.7 also was more strict about parsing, and rejected messages with initial "From " headers. This restriction is relaxed in [2]. For reasons explained in [3], we want to keep this more relaxed parsing for now. [1] https://bugzilla.gnome.org/show_bug.cgi?id=668085 [2] http://git.gnome.org/browse/gmime/commit/?id=d311f576baf750476e06e9a1367a2dc1793ea7eb [3] id:"1331385931-1610-1-git-send-email-david@tethera.net" --- configure | 4 +++- test/crypto | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure b/configure index dedb7d8..ee0ae73 100755 --- a/configure +++ b/configure @@ -273,9 +273,11 @@ if [ ${have_xapian} = "0" ]; then errors=$((errors + 1)) fi +# If using GMime 2.6, we need to have a version >= 2.6.5 to avoid a +# crypto bug. We need 2.6.7 for permissive "From " header handling. printf "Checking for GMime development files... " have_gmime=0 -for gmimepc in gmime-2.6 gmime-2.4; do +for gmimepc in 'gmime-2.6 >= 2.6.7' gmime-2.4; do if pkg-config --exists $gmimepc; then printf "Yes ($gmimepc).\n" have_gmime=1 diff --git a/test/crypto b/test/crypto index 1dbb60a..6723ef8 100755 --- a/test/crypto +++ b/test/crypto @@ -104,8 +104,6 @@ test_expect_equal \ "$expected" test_begin_subtest "signature verification with signer key unavailable" -# this is broken with current versions of gmime-2.6 -(ldd $(which notmuch) | grep -Fq gmime-2.6) && test_subtest_known_broken # move the gnupghome temporarily out of the way mv "${GNUPGHOME}"{,.bak} output=$(notmuch show --format=json --verify subject:"test signed message 001" \ -- cgit v1.2.3 From d379e3a0e23f3af9d8dece4bd21f9393d9f42249 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sun, 11 Mar 2012 23:36:16 +0200 Subject: man: update SEE ALSO references in man pages Drop references to notmuch-part(1). Reference all man pages. Fix man page section of notmuch-dump(1). --- man/man1/notmuch-config.1 | 5 ++--- man/man1/notmuch-count.1 | 8 ++++---- man/man1/notmuch-dump.1 | 8 ++++---- man/man1/notmuch-new.1 | 8 ++++---- man/man1/notmuch-reply.1 | 8 ++++---- man/man1/notmuch-restore.1 | 8 ++++---- man/man1/notmuch-search.1 | 8 ++++---- man/man1/notmuch-show.1 | 8 ++++---- man/man1/notmuch-tag.1 | 8 ++++---- man/man1/notmuch.1 | 7 +++---- man/man5/notmuch-hooks.5 | 8 ++++---- man/man7/notmuch-search-terms.7 | 5 ++--- 12 files changed, 43 insertions(+), 46 deletions(-) diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index 57eee93..2205d28 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -129,9 +129,8 @@ Specifies the location of the notmuch configuration file. Notmuch will use ${HOME}/.notmuch\-config if this variable is not set. .SH SEE ALSO -\fBnotmuch\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), -\fBnotmuch-part\fR(1), \fBnotmuch-reply\fR(1), +\fBnotmuch\fR(1), \fBnotmuch-count\fR(1), \fBnotmuch-dump\fR(1), +\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-count.1 b/man/man1/notmuch-count.1 index 0d0ab5d..79dd6a5 100644 --- a/man/man1/notmuch-count.1 +++ b/man/man1/notmuch-count.1 @@ -44,7 +44,7 @@ Output the number of matching threads. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-dump\fR(1), -\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-part\fR(1), -\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1), +\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-dump.1 b/man/man1/notmuch-dump.1 index bd7e274..4e24392 100644 --- a/man/man1/notmuch-dump.1 +++ b/man/man1/notmuch-dump.1 @@ -31,7 +31,7 @@ for details of the supported syntax for . .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-part\fR(1), -\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1), +\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-new.1 b/man/man1/notmuch-new.1 index ccee738..19cef3d 100644 --- a/man/man1/notmuch-new.1 +++ b/man/man1/notmuch-new.1 @@ -64,7 +64,7 @@ Prevents hooks from being run. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-hooks\fR(5), \fBnotmuch-part\fR(1), -\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-reply\fR(1), +\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index 7ed7f0f..0fb68b4 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -80,7 +80,7 @@ on issue found in multiple patches. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), -\fBnotmuch-part\fR(1), \fBnotmuch-restore\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), +\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1 index 8bd59e4..bcb765c 100644 --- a/man/man1/notmuch-restore.1 +++ b/man/man1/notmuch-restore.1 @@ -39,7 +39,7 @@ details. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-part\fR(1), -\fBnotmuch-reply\fR(1), \fBnotmuch-dump\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), +\fBnotmuch-reply\fR(1), \fBnotmuch-search\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1 index 19d85df..72874d0 100644 --- a/man/man1/notmuch-search.1 +++ b/man/man1/notmuch-search.1 @@ -115,7 +115,7 @@ Limit the number of displayed results to N. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-hooks\fR(5), \fBnotmuch-part\fR(1), -\fBnotmuch-reply\fR(1), \fBnotmuch-reply\fR(1), -\fBnotmuch-restore\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), +\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index 4c5db94..27a06b7 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -139,7 +139,7 @@ command. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), -\fBnotmuch-part\fR(1), \fBnotmuch-reply\fR(1), -\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), -\fBnotmuch-search-terms\fR(7), \fBnotmuch-tag\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), +\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), +\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), +\fBnotmuch-tag\fR(1) diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1 index 8e0bb5c..9f62222 100644 --- a/man/man1/notmuch-tag.1 +++ b/man/man1/notmuch-tag.1 @@ -32,7 +32,7 @@ details. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), -\fBnotmuch-part\fR(1), \fBnotmuch-reply\fR(1), -\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), -\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), +\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), +\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), +\fBnotmuch-show\fR(1), diff --git a/man/man1/notmuch.1 b/man/man1/notmuch.1 index 3ab1af5..6fd20e0 100644 --- a/man/man1/notmuch.1 +++ b/man/man1/notmuch.1 @@ -131,10 +131,9 @@ use ${HOME}/.notmuch\-config if this variable is not set. \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), \fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), -\fBnotmuch-part\fR(1), \fBnotmuch-reply\fR(1), -\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), -\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), -\fBnotmuch-tag\fR(1) +\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), +\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), +\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) The notmuch website: diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5 index 1e459c0..ab40443 100644 --- a/man/man5/notmuch-hooks.5 +++ b/man/man5/notmuch-hooks.5 @@ -42,7 +42,7 @@ imported messages. .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-part\fR(1), -\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-dump\fR(1), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1), +\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1), +\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1), +\fBnotmuch-tag\fR(1) diff --git a/man/man7/notmuch-search-terms.7 b/man/man7/notmuch-search-terms.7 index 09a699f..1d43042 100644 --- a/man/man7/notmuch-search-terms.7 +++ b/man/man7/notmuch-search-terms.7 @@ -135,7 +135,6 @@ current time: .SH SEE ALSO \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1), -\fBnotmuch-dump\fR(5), \fBnotmuch-hooks\fR(5), \fBnotmuch-part\fR(1), +\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), -\fBnotmuch-search\fR(1), \fBnotmuch-search\fR(1), -\fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) +\fBnotmuch-search\fR(1), \fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1) -- cgit v1.2.3 From eae27dd5cc2224e4367aa402bdaea38a31649d83 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 13 Mar 2012 21:40:40 -0300 Subject: debian: bump gmime dependency to 2.6.7 As explained in f34613e, if using gmime 2.6, we want at least 2.6.7 --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index e1ac1a7..ed27c58 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 7.0.50~), pkg-config, libxapian-dev, - libgmime-2.6-dev | libgmime-2.4-dev, + libgmime-2.6-dev (>= 2.6.7~) | libgmime-2.4-dev, libtalloc-dev, libz-dev, python-all (>= 2.6.6-3~), -- cgit v1.2.3 From bd99627e6d5d8b88e1b790557c91bff3be337c8e Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 14 Mar 2012 23:21:06 +0200 Subject: NEWS: emacs: Fix MML tag quoting in replies --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 4152e7c..6778442 100644 --- a/NEWS +++ b/NEWS @@ -68,6 +68,12 @@ New functions optionally visiting a URI to the current message at one of a number of Mailing List Archives. +Fix MML tag quoting in replies + + The MML tag quoting fix of 0.11.1 unintentionally quoted tags + inserted in `message-setup-hook'. Quoting is now limited to the + cited message. + Library changes --------------- -- cgit v1.2.3 From f9fd98142ba1f5e2042dba8b8476e6afc6bfa402 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 14 Mar 2012 23:21:07 +0200 Subject: NEWS: emacs: show view archiving key binding changes --- NEWS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS b/NEWS index 6778442..86c0690 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,15 @@ Fix MML tag quoting in replies inserted in `message-setup-hook'. Quoting is now limited to the cited message. +Show view archiving key binding changes + + The show view archiving key bindings 'a' and 'x' now remove the + "inbox" tag from the current message only (instead of thread), and + move to the next message. At the last message, 'a' proceeds to the + next thread in search results, and 'x' returns to search + results. The thread archiving functions are now available in 'A' and + 'X'. + Library changes --------------- -- cgit v1.2.3 From 8d479ce3e80453c6497753ba490502777b6f66f3 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 14 Mar 2012 23:21:08 +0200 Subject: NEWS: emacs: support text/calendar MIME type --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 86c0690..3c2cd82 100644 --- a/NEWS +++ b/NEWS @@ -83,6 +83,11 @@ Show view archiving key binding changes results. The thread archiving functions are now available in 'A' and 'X'. +Support text/calendar MIME type + + The text/calendar MIME type is now supported in addition to + text/x-vcalendar. + Library changes --------------- -- cgit v1.2.3 From 743d3bcdd8e84bc35051778c42b402ffb747e599 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 14 Mar 2012 23:21:09 +0200 Subject: NEWS: emacs: inline patch fake attachment file names --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 3c2cd82..83d764a 100644 --- a/NEWS +++ b/NEWS @@ -88,6 +88,13 @@ Support text/calendar MIME type The text/calendar MIME type is now supported in addition to text/x-vcalendar. +Generate inline patch fake attachment file names from message subject + + Use the message subject to generate file names for the inline patch + fake attachments. The names are now similar to the ones generated by + 'git format-patch' instead of just "inline patch". See "Notmuch Show + Insert Text/Plain Hook" in the notmuch customize interface. + Library changes --------------- -- cgit v1.2.3 From d8b131c37dccf3c09a61dbd10b543b75903d9e76 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 14 Mar 2012 23:21:11 +0200 Subject: NEWS: cli: manual page for notmuch configuration options --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 83d764a..18760ce 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,12 @@ Mail store folder/file ignore NOTE: *Every* file/directory that goes by one of those names will be ignored, independent of its depth/location in the mail store. +Manual page for notmuch configuration options + + The notmuch CLI configuration file options are now documented in the + notmuch-config(1) manual page in addition to the configuration file + itself. + Emacs Interface --------------- -- cgit v1.2.3 From 622d2f6128a70ff010586a211d4b4af19650064b Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 14 Mar 2012 23:21:10 +0200 Subject: NEWS: emacs: enable `notmuch-search-line-faces' by default --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 18760ce..09c5d9c 100644 --- a/NEWS +++ b/NEWS @@ -101,6 +101,12 @@ Generate inline patch fake attachment file names from message subject 'git format-patch' instead of just "inline patch". See "Notmuch Show Insert Text/Plain Hook" in the notmuch customize interface. +Enable `notmuch-search-line-faces' by default + + Make the `notmuch-search-line-faces' functionality more discoverable + for new users by showing "unread" messages bold and "flagged" + messages blue by default in the search view. + Library changes --------------- -- cgit v1.2.3 From ea54c4fdc7d0ed9a4d6ab328d44c10ce5668d587 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Sat, 17 Mar 2012 17:41:27 +0100 Subject: Fix error reporting in notmuch_database_find_message_by_filename Formerly it was possible for *message_ret to be left uninitialized. The documentation however clearly states that "[o]n any failure or when the message is not found, this function initializes '*message' to NULL". Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- lib/database.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/database.cc b/lib/database.cc index 8f8df1a..16c4354 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1825,6 +1825,9 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, if (message_ret == NULL) return NOTMUCH_STATUS_NULL_POINTER; + /* return NULL on any failure */ + *message_ret = NULL; + local = talloc_new (notmuch); try { -- cgit v1.2.3 From 3a95f3fbc1602d612aec2b7f1095e40c95723aee Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 16 Mar 2012 13:56:32 +0100 Subject: python: fix signature of two wrapped libnotmuch functions Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- bindings/python/notmuch/directory.py | 2 +- bindings/python/notmuch/threads.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py index 0c5e015..284cbdc 100644 --- a/bindings/python/notmuch/directory.py +++ b/bindings/python/notmuch/directory.py @@ -177,7 +177,7 @@ class Directory(object): _destroy = nmlib.notmuch_directory_destroy _destroy.argtypes = [NotmuchDirectoryP] - _destroy.argtypes = None + _destroy.restype = None def __del__(self): """Close and free the Directory""" diff --git a/bindings/python/notmuch/threads.py b/bindings/python/notmuch/threads.py index 690206e..225f524 100644 --- a/bindings/python/notmuch/threads.py +++ b/bindings/python/notmuch/threads.py @@ -172,7 +172,7 @@ class Threads(Python3StringMixIn): _destroy = nmlib.notmuch_threads_destroy _destroy.argtypes = [NotmuchThreadsP] - _destroy.argtypes = None + _destroy.restype = None def __del__(self): """Close and free the notmuch Threads""" -- cgit v1.2.3 From d71cb5e45b2e27dea30bbca167af54f8403ff8a5 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 18 Mar 2012 08:07:00 -0300 Subject: bump version to 0.12~rc2 --- bindings/python/notmuch/version.py | 2 +- man/man1/notmuch-config.1 | 2 +- man/man1/notmuch-count.1 | 2 +- man/man1/notmuch-dump.1 | 2 +- man/man1/notmuch-new.1 | 2 +- man/man1/notmuch-reply.1 | 2 +- man/man1/notmuch-restore.1 | 2 +- man/man1/notmuch-search.1 | 2 +- man/man1/notmuch-show.1 | 2 +- man/man1/notmuch-tag.1 | 2 +- man/man1/notmuch.1 | 2 +- man/man5/notmuch-hooks.5 | 2 +- man/man7/notmuch-search-terms.7 | 2 +- version | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index e58a1b5..8f7eb87 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,2 +1,2 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.12~rc1' +__VERSION__ = '0.12~rc2' diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index 2205d28..fb023b8 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-CONFIG 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-CONFIG 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-config \- Access notmuch configuration file. .SH SYNOPSIS diff --git a/man/man1/notmuch-count.1 b/man/man1/notmuch-count.1 index 79dd6a5..383252e 100644 --- a/man/man1/notmuch-count.1 +++ b/man/man1/notmuch-count.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-COUNT 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-COUNT 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-count \- Count messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-dump.1 b/man/man1/notmuch-dump.1 index 4e24392..7cfe08e 100644 --- a/man/man1/notmuch-dump.1 +++ b/man/man1/notmuch-dump.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-DUMP 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-DUMP 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-dump \- Creates a plain-text dump of the tags of each message. diff --git a/man/man1/notmuch-new.1 b/man/man1/notmuch-new.1 index 19cef3d..1158bb2 100644 --- a/man/man1/notmuch-new.1 +++ b/man/man1/notmuch-new.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-NEW 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-NEW 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-new \- Incorporate new mail into the notmuch database. .SH SYNOPSIS diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index 0fb68b4..deb3ae1 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-REPLY 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-REPLY 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-reply \- Constructs a reply template for a set of messages. diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1 index bcb765c..333f488 100644 --- a/man/man1/notmuch-restore.1 +++ b/man/man1/notmuch-restore.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-RESTORE 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-RESTORE 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-restore \- Restores the tags from the given file (see notmuch dump). diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1 index 72874d0..2cf830a 100644 --- a/man/man1/notmuch-search.1 +++ b/man/man1/notmuch-search.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SEARCH 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-SEARCH 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-search \- Search for messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index 27a06b7..48c7e0b 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SHOW 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-SHOW 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-show \- Show messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1 index 9f62222..8a010e4 100644 --- a/man/man1/notmuch-tag.1 +++ b/man/man1/notmuch-tag.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-TAG 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-TAG 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-tag \- Add/remove tags for all messages matching the search terms. diff --git a/man/man1/notmuch.1 b/man/man1/notmuch.1 index 6fd20e0..3984314 100644 --- a/man/man1/notmuch.1 +++ b/man/man1/notmuch.1 @@ -16,7 +16,7 @@ .\" along with this program. If not, see http://www.gnu.org/licenses/ . .\" .\" Author: Carl Worth -.TH NOTMUCH 1 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH 1 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch \- thread-based email index, search, and tagging .SH SYNOPSIS diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5 index ab40443..2397c7e 100644 --- a/man/man5/notmuch-hooks.5 +++ b/man/man5/notmuch-hooks.5 @@ -1,4 +1,4 @@ -.TH NOTMUCH-HOOKS 5 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-HOOKS 5 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-hooks \- hooks for notmuch diff --git a/man/man7/notmuch-search-terms.7 b/man/man7/notmuch-search-terms.7 index 1d43042..a2dde70 100644 --- a/man/man7/notmuch-search-terms.7 +++ b/man/man7/notmuch-search-terms.7 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SEARCH-TERMS 7 2012-02-29 "Notmuch 0.12~rc1" +.TH NOTMUCH-SEARCH-TERMS 7 2012-03-18 "Notmuch 0.12~rc2" .SH NAME notmuch-search-terms \- Syntax for notmuch queries diff --git a/version b/version index 0d4c53f..d885035 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.12~rc1 +0.12~rc2 -- cgit v1.2.3 From 9325cae5f46e543aedb790cfe62a4faabcba949c Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 18 Mar 2012 08:16:55 -0300 Subject: debian: changelog stanza for 0.12~rc2 mention the two bugfixes --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 147ae36..9cf58ae 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +notmuch (0.12~rc2-1) experimental; urgency=low + + * Upstream pre-release + * New bug fixes since ~rc1 + - fix for uninitialized variable + - fix for python bindings type signatures + + -- David Bremner Sun, 18 Mar 2012 08:10:35 -0300 + notmuch (0.12~rc1-1) experimental; urgency=low * Upstream pre-release. -- cgit v1.2.3 From d2e96a35a5e5eceec158dc255331b6c524810b83 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:37 +0000 Subject: test: Fix typo in test description Part 4 is a multipart, not an html part. --- test/multipart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/multipart b/test/multipart index 53782c6..0ac96d5 100755 --- a/test/multipart +++ b/test/multipart @@ -496,7 +496,7 @@ test_subtest_known_broken notmuch show --format=raw --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT test_expect_equal_file OUTPUT embedded_message -test_begin_subtest "--format=raw --part=4, rfc822's html part" +test_begin_subtest "--format=raw --part=4, rfc822's multipart" notmuch show --format=raw --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED

This is an embedded message, with a multipart/alternative part.

-- cgit v1.2.3 From 046ab77b1045596ab225296987401fe95eb2fe13 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:38 +0000 Subject: test: Fix malformed multipart message Previously, there was only one CRLF between the terminating boundary of the embedded multipart/alternative and the boundary of the containing multipart. However, according the RFC 1341, 7.2.1: The boundary must be followed immediately either by another CRLF and the header fields for the next part, or by two CRLFs, in which case there are no header fields for the next part and The CRLF preceding the encapsulation line is considered part of the boundary so that it is possible to have a part that does not end with a CRLF (line break). Thus, there must be *two* CRLFs between these boundaries: one that ends the terminating boundary and one that begins the enclosing boundary. While GMime accepted the message we had before, it could not produce such a message. --- test/multipart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/multipart b/test/multipart index 0ac96d5..e73cd8b 100755 --- a/test/multipart +++ b/test/multipart @@ -46,6 +46,7 @@ Content-Disposition: inline EOF cat embedded_message >> ${MAIL_DIR}/multipart cat <> ${MAIL_DIR}/multipart + --=-=-= Content-Disposition: attachment; filename=attachment -- cgit v1.2.3 From 6a4df1b796ea82545fa0861d341aa6769f72ac02 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:39 +0000 Subject: show: Allow formatters to return errors Formatter errors are propagated to the exit status of notmuch show. This isn't used by the JSON or text formatters, but it will be useful for the raw format, which is pickier. --- notmuch-client.h | 6 ++--- notmuch-show.c | 71 ++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index f4a62cc..a220fe4 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -67,9 +67,9 @@ struct notmuch_show_params; typedef struct notmuch_show_format { const char *message_set_start; - void (*part) (const void *ctx, - struct mime_node *node, int indent, - const struct notmuch_show_params *params); + notmuch_status_t (*part) (const void *ctx, + struct mime_node *node, int indent, + const struct notmuch_show_params *params); const char *message_start; void (*message) (const void *ctx, notmuch_message_t *message, diff --git a/notmuch-show.c b/notmuch-show.c index 05d51b2..8a8c0fc 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -23,7 +23,7 @@ static void format_headers_message_part_text (GMimeMessage *message); -static void +static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); @@ -34,7 +34,7 @@ static const notmuch_show_format_t format_text = { .message_set_end = "" }; -static void +static notmuch_status_t format_part_json_entry (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); @@ -563,7 +563,7 @@ format_part_content_raw (GMimeObject *part) g_object_unref(stream_stdout); } -static void +static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params) { @@ -652,6 +652,8 @@ format_part_text (const void *ctx, mime_node_t *node, printf ("\fbody}\n"); printf ("\f%s}\n", part_type); + + return NOTMUCH_STATUS_SUCCESS; } static void @@ -753,14 +755,16 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) printf ("%s}", terminator); } -static void +static notmuch_status_t format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), unused (const notmuch_show_params_t *params)) { format_part_json (ctx, node, TRUE); + + return NOTMUCH_STATUS_SUCCESS; } -static void +static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, notmuch_message_t *message, @@ -770,14 +774,18 @@ show_message (void *ctx, if (format->part) { void *local = talloc_new (ctx); mime_node_t *root, *part; - - if (mime_node_open (local, message, params->cryptoctx, params->decrypt, - &root) == NOTMUCH_STATUS_SUCCESS && - (part = mime_node_seek_dfs (root, (params->part < 0 ? - 0 : params->part)))) - format->part (local, part, indent, params); + notmuch_status_t status; + + status = mime_node_open (local, message, params->cryptoctx, + params->decrypt, &root); + if (status) + goto DONE; + part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part)); + if (part) + status = format->part (local, part, indent, params); + DONE: talloc_free (local); - return; + return status; } if (params->part <= 0) { @@ -801,9 +809,11 @@ show_message (void *ctx, fputs (format->message_end, stdout); } + + return NOTMUCH_STATUS_SUCCESS; } -static void +static notmuch_status_t show_messages (void *ctx, const notmuch_show_format_t *format, notmuch_messages_t *messages, @@ -814,6 +824,7 @@ show_messages (void *ctx, notmuch_bool_t match; int first_set = 1; int next_indent; + notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; fputs (format->message_set_start, stdout); @@ -834,17 +845,22 @@ show_messages (void *ctx, next_indent = indent; if (match || params->entire_thread) { - show_message (ctx, format, message, indent, params); + status = show_message (ctx, format, message, indent, params); + if (status && !res) + res = status; next_indent = indent + 1; - fputs (format->message_set_sep, stdout); + if (!status) + fputs (format->message_set_sep, stdout); } - show_messages (ctx, - format, - notmuch_message_get_replies (message), - next_indent, - params); + status = show_messages (ctx, + format, + notmuch_message_get_replies (message), + next_indent, + params); + if (status && !res) + res = status; notmuch_message_destroy (message); @@ -852,6 +868,8 @@ show_messages (void *ctx, } fputs (format->message_set_end, stdout); + + return res; } /* Formatted output of single message */ @@ -916,13 +934,13 @@ do_show_single (void *ctx, fclose (file); + return 0; + } else { - show_message (ctx, format, message, 0, params); + return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; } - - return 0; } /* Formatted output of threads */ @@ -936,6 +954,7 @@ do_show (void *ctx, notmuch_thread_t *thread; notmuch_messages_t *messages; int first_toplevel = 1; + notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; fputs (format->message_set_start, stdout); @@ -955,7 +974,9 @@ do_show (void *ctx, fputs (format->message_set_sep, stdout); first_toplevel = 0; - show_messages (ctx, format, messages, 0, params); + status = show_messages (ctx, format, messages, 0, params); + if (status && !res) + res = status; notmuch_thread_destroy (thread); @@ -963,7 +984,7 @@ do_show (void *ctx, fputs (format->message_set_end, stdout); - return 0; + return res != NOTMUCH_STATUS_SUCCESS; } enum { -- cgit v1.2.3 From 8d01b0749c16bab0f0e01066a9afa9786193e739 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:40 +0000 Subject: show: Move format_message_mbox with the other new-style formats Just code motion. --- notmuch-show.c | 100 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 8a8c0fc..f13e187 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -220,56 +220,6 @@ _is_from_line (const char *line) return 0; } -/* Print a message in "mboxrd" format as documented, for example, - * here: - * - * http://qmail.org/qmail-manual-html/man5/mbox.html - */ -static void -format_message_mbox (const void *ctx, - notmuch_message_t *message, - unused (int indent)) -{ - const char *filename; - FILE *file; - const char *from; - - time_t date; - struct tm date_gmtime; - char date_asctime[26]; - - char *line = NULL; - size_t line_size; - ssize_t line_len; - - filename = notmuch_message_get_filename (message); - file = fopen (filename, "r"); - if (file == NULL) { - fprintf (stderr, "Failed to open %s: %s\n", - filename, strerror (errno)); - return; - } - - from = notmuch_message_get_header (message, "from"); - from = _extract_email_address (ctx, from); - - date = notmuch_message_get_date (message); - gmtime_r (&date, &date_gmtime); - asctime_r (&date_gmtime, date_asctime); - - printf ("From %s %s", from, date_asctime); - - while ((line_len = getline (&line, &line_size, file)) != -1 ) { - if (_is_from_line (line)) - putchar ('>'); - printf ("%s", line); - } - - printf ("\n"); - - fclose (file); -} - static void format_headers_message_part_text (GMimeMessage *message) { @@ -764,6 +714,56 @@ format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), return NOTMUCH_STATUS_SUCCESS; } +/* Print a message in "mboxrd" format as documented, for example, + * here: + * + * http://qmail.org/qmail-manual-html/man5/mbox.html + */ +static void +format_message_mbox (const void *ctx, + notmuch_message_t *message, + unused (int indent)) +{ + const char *filename; + FILE *file; + const char *from; + + time_t date; + struct tm date_gmtime; + char date_asctime[26]; + + char *line = NULL; + size_t line_size; + ssize_t line_len; + + filename = notmuch_message_get_filename (message); + file = fopen (filename, "r"); + if (file == NULL) { + fprintf (stderr, "Failed to open %s: %s\n", + filename, strerror (errno)); + return; + } + + from = notmuch_message_get_header (message, "from"); + from = _extract_email_address (ctx, from); + + date = notmuch_message_get_date (message); + gmtime_r (&date, &date_gmtime); + asctime_r (&date_gmtime, date_asctime); + + printf ("From %s %s", from, date_asctime); + + while ((line_len = getline (&line, &line_size, file)) != -1 ) { + if (_is_from_line (line)) + putchar ('>'); + printf ("%s", line); + } + + printf ("\n"); + + fclose (file); +} + static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From d4312393530f70416612cf88dccc2620412a2bc6 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:41 +0000 Subject: show: Convert mbox format to new self-recursive style Given the lack of recursion, this is pretty easy. --- notmuch-show.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index f13e187..353bda5 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -45,25 +45,15 @@ static const notmuch_show_format_t format_json = { .message_set_end = "]" }; -static void -format_message_mbox (const void *ctx, - notmuch_message_t *message, - unused (int indent)); +static notmuch_status_t +format_part_mbox (const void *ctx, mime_node_t *node, + int indent, const notmuch_show_params_t *params); static const notmuch_show_format_t format_mbox = { - "", NULL, - "", format_message_mbox, - "", NULL, NULL, "", - "", - NULL, - NULL, - NULL, - NULL, - NULL, - "", - "", - "", "", - "" + .message_set_start = "", + .part = format_part_mbox, + .message_set_sep = "", + .message_set_end = "" }; static void @@ -719,11 +709,12 @@ format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent), * * http://qmail.org/qmail-manual-html/man5/mbox.html */ -static void -format_message_mbox (const void *ctx, - notmuch_message_t *message, - unused (int indent)) +static notmuch_status_t +format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), + unused (const notmuch_show_params_t *params)) { + notmuch_message_t *message = node->envelope_file; + const char *filename; FILE *file; const char *from; @@ -736,12 +727,15 @@ format_message_mbox (const void *ctx, size_t line_size; ssize_t line_len; + if (!message) + INTERNAL_ERROR ("format_part_mbox requires a root part"); + filename = notmuch_message_get_filename (message); file = fopen (filename, "r"); if (file == NULL) { fprintf (stderr, "Failed to open %s: %s\n", filename, strerror (errno)); - return; + return NOTMUCH_STATUS_FILE_ERROR; } from = notmuch_message_get_header (message, "from"); @@ -762,6 +756,8 @@ format_message_mbox (const void *ctx, printf ("\n"); fclose (file); + + return NOTMUCH_STATUS_SUCCESS; } static notmuch_status_t -- cgit v1.2.3 From 7e1742a82c4b52f5337d8db53347d547a4ac67b4 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:42 +0000 Subject: show: Move format_part_content_raw with the other new-style formats Just code motion. --- notmuch-show.c | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 353bda5..341634d 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -476,33 +476,6 @@ format_part_sigstatus_json (mime_node_t *node) } #endif -static void -format_part_content_raw (GMimeObject *part) -{ - if (! GMIME_IS_PART (part)) - return; - - GMimeStream *stream_stdout; - GMimeStream *stream_filter = NULL; - GMimeDataWrapper *wrapper; - - stream_stdout = g_mime_stream_file_new (stdout); - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); - - stream_filter = g_mime_stream_filter_new (stream_stdout); - - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); - - if (wrapper && stream_filter) - g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); - - if (stream_filter) - g_object_unref (stream_filter); - - if (stream_stdout) - g_object_unref(stream_stdout); -} - static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params) @@ -760,6 +733,33 @@ format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), return NOTMUCH_STATUS_SUCCESS; } +static void +format_part_content_raw (GMimeObject *part) +{ + if (! GMIME_IS_PART (part)) + return; + + GMimeStream *stream_stdout; + GMimeStream *stream_filter = NULL; + GMimeDataWrapper *wrapper; + + stream_stdout = g_mime_stream_file_new (stdout); + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); + + stream_filter = g_mime_stream_filter_new (stream_stdout); + + wrapper = g_mime_part_get_content_object (GMIME_PART (part)); + + if (wrapper && stream_filter) + g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + + if (stream_filter) + g_object_unref (stream_filter); + + if (stream_stdout) + g_object_unref(stream_stdout); +} + static notmuch_status_t show_message (void *ctx, const notmuch_show_format_t *format, -- cgit v1.2.3 From b1130bc71c02efb504ad723b56f86618fd186a67 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:43 +0000 Subject: show: Convert raw format to the new self-recursive style, properly support interior parts This is fully compatible for root and leaf parts, but now has proper support for interior parts. This requires some design decisions that were guided by what I would want if I were to save a part. Specifically: - Leaf parts are printed without headers and with transfer decoding. This is what makes sense for saving attachments. (Furthermore, the transfer decoding is necessary since, without the headers, the caller would not be able to interpret non-transfer-decoded output.) - Message parts are printed with their message headers, but without enclosing part headers. This is what makes sense for saving a message as a whole (which is a message part) and for saving attached messages. This is symmetric for whole messages and for attached messages, though we special-case the whole message for performance reasons (and corner-case correctness reasons: given malformed input, GMime may not be able to reproduce it from the parsed representation). - Multipart parts are printed with their headers and all child parts. It's not clear what the best thing to do for multipart is, but this was the most natural to implement and can be justified because such parts can't be interpreted without their headers. As an added benefit, we can move the special-case code for part 0 into the raw formatter. --- notmuch-show.c | 159 ++++++++++++++++++++++++--------------------------------- test/multipart | 74 +++++++++++++++++---------- 2 files changed, 116 insertions(+), 117 deletions(-) diff --git a/notmuch-show.c b/notmuch-show.c index 341634d..a7463dc 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -20,9 +20,6 @@ #include "notmuch-client.h" -static void -format_headers_message_part_text (GMimeMessage *message); - static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params); @@ -56,23 +53,16 @@ static const notmuch_show_format_t format_mbox = { .message_set_end = "" }; -static void -format_part_content_raw (GMimeObject *part); +static notmuch_status_t +format_part_raw (unused (const void *ctx), mime_node_t *node, + unused (int indent), + unused (const notmuch_show_params_t *params)); static const notmuch_show_format_t format_raw = { - "", NULL, - "", NULL, - "", NULL, format_headers_message_part_text, "\n", - "", - NULL, - NULL, - NULL, - format_part_content_raw, - NULL, - "", - "", - "", "", - "" + .message_set_start = "", + .part = format_part_raw, + .message_set_sep = "", + .message_set_end = "" }; static const char * @@ -210,27 +200,6 @@ _is_from_line (const char *line) return 0; } -static void -format_headers_message_part_text (GMimeMessage *message) -{ - InternetAddressList *recipients; - const char *recipients_string; - - printf ("Subject: %s\n", g_mime_message_get_subject (message)); - printf ("From: %s\n", g_mime_message_get_sender (message)); - recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); - recipients_string = internet_address_list_to_string (recipients, 0); - if (recipients_string) - printf ("To: %s\n", - recipients_string); - recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); - recipients_string = internet_address_list_to_string (recipients, 0); - if (recipients_string) - printf ("Cc: %s\n", - recipients_string); - printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); -} - static void format_headers_json (const void *ctx, GMimeMessage *message) { @@ -733,31 +702,82 @@ format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent), return NOTMUCH_STATUS_SUCCESS; } -static void -format_part_content_raw (GMimeObject *part) +static notmuch_status_t +format_part_raw (unused (const void *ctx), mime_node_t *node, + unused (int indent), + unused (const notmuch_show_params_t *params)) { - if (! GMIME_IS_PART (part)) - return; + if (node->envelope_file) { + /* Special case the entire message to avoid MIME parsing. */ + const char *filename; + FILE *file; + size_t size; + char buf[4096]; + + filename = notmuch_message_get_filename (node->envelope_file); + if (filename == NULL) { + fprintf (stderr, "Error: Cannot get message filename.\n"); + return NOTMUCH_STATUS_FILE_ERROR; + } + + file = fopen (filename, "r"); + if (file == NULL) { + fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno)); + return NOTMUCH_STATUS_FILE_ERROR; + } + + while (!feof (file)) { + size = fread (buf, 1, sizeof (buf), file); + if (ferror (file)) { + fprintf (stderr, "Error: Read failed from %s\n", filename); + fclose (file); + return NOTMUCH_STATUS_FILE_ERROR; + } + + if (fwrite (buf, size, 1, stdout) != 1) { + fprintf (stderr, "Error: Write failed\n"); + fclose (file); + return NOTMUCH_STATUS_FILE_ERROR; + } + } + + fclose (file); + return NOTMUCH_STATUS_SUCCESS; + } GMimeStream *stream_stdout; GMimeStream *stream_filter = NULL; - GMimeDataWrapper *wrapper; stream_stdout = g_mime_stream_file_new (stdout); g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); stream_filter = g_mime_stream_filter_new (stream_stdout); - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); + if (GMIME_IS_PART (node->part)) { + /* For leaf parts, we emit only the transfer-decoded + * body. */ + GMimeDataWrapper *wrapper; + wrapper = g_mime_part_get_content_object (GMIME_PART (node->part)); - if (wrapper && stream_filter) - g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + if (wrapper && stream_filter) + g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); + } else { + /* Write out the whole part. For message parts (the root + * part and embedded message parts), this will be the + * message including its headers (but not the + * encapsulating part's headers). For multipart parts, + * this will include the headers. */ + if (stream_filter) + g_mime_object_write_to_stream (node->part, stream_filter); + } if (stream_filter) g_object_unref (stream_filter); if (stream_stdout) g_object_unref(stream_stdout); + + return NOTMUCH_STATUS_SUCCESS; } static notmuch_status_t @@ -893,50 +913,7 @@ do_show_single (void *ctx, notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1); - /* Special case for --format=raw of full single message, just cat out file */ - if (params->raw && 0 == params->part) { - - const char *filename; - FILE *file; - size_t size; - char buf[4096]; - - filename = notmuch_message_get_filename (message); - if (filename == NULL) { - fprintf (stderr, "Error: Cannot message filename.\n"); - return 1; - } - - file = fopen (filename, "r"); - if (file == NULL) { - fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno)); - return 1; - } - - while (!feof (file)) { - size = fread (buf, 1, sizeof (buf), file); - if (ferror (file)) { - fprintf (stderr, "Error: Read failed from %s\n", filename); - fclose (file); - return 1; - } - - if (fwrite (buf, size, 1, stdout) != 1) { - fprintf (stderr, "Error: Write failed\n"); - fclose (file); - return 1; - } - } - - fclose (file); - - return 0; - - } else { - - return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; - - } + return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS; } /* Formatted output of threads */ diff --git a/test/multipart b/test/multipart index e73cd8b..afc4fc8 100755 --- a/test/multipart +++ b/test/multipart @@ -450,58 +450,80 @@ test_expect_equal_file OUTPUT "${MAIL_DIR}"/multipart test_begin_subtest "--format=raw --part=1, message body" notmuch show --format=raw --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT -# output should *not* include newline -echo >>OUTPUT -cat <EXPECTED -Subject: html message -From: Carl Worth -To: cworth@cworth.org -Date: Fri, 05 Jan 2001 15:42:57 +0000 - -

This is an embedded message, with a multipart/alternative part.

-This is an embedded message, with a multipart/alternative part. -This is a text attachment. -And this message is signed. - --Carl ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1.4.11 (GNU/Linux) - -iEYEARECAAYFAk3SA/gACgkQ6JDdNq8qSWj0sACghqVJEQJUs3yV8zbTzhgnSIcD -W6cAmQE4dcYrx/LPLtYLZm1jsGauE5hE -=zkga ------END PGP SIGNATURE----- -EOF -test_expect_equal_file OUTPUT EXPECTED +test_expect_equal_file OUTPUT "${MAIL_DIR}"/multipart test_begin_subtest "--format=raw --part=2, multipart/mixed" notmuch show --format=raw --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED -Subject: html message +Content-Type: multipart/mixed; boundary="=-=-=" + +--=-=-= +Content-Type: message/rfc822 +Content-Disposition: inline + From: Carl Worth To: cworth@cworth.org +Subject: html message Date: Fri, 05 Jan 2001 15:42:57 +0000 +User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu) +Message-ID: <87liy5ap01.fsf@yoom.home.cworth.org> +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="==-=-==" + +--==-=-== +Content-Type: text/html

This is an embedded message, with a multipart/alternative part.

+ +--==-=-== +Content-Type: text/plain + This is an embedded message, with a multipart/alternative part. + +--==-=-==-- + +--=-=-= +Content-Disposition: attachment; filename=attachment + This is a text attachment. + +--=-=-= + And this message is signed. -Carl + +--=-=-=-- EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "--format=raw --part=3, rfc822 part" -test_subtest_known_broken - notmuch show --format=raw --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT test_expect_equal_file OUTPUT embedded_message test_begin_subtest "--format=raw --part=4, rfc822's multipart" notmuch show --format=raw --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT cat <EXPECTED +From: Carl Worth +To: cworth@cworth.org +Subject: html message +Date: Fri, 05 Jan 2001 15:42:57 +0000 +User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu) +Message-ID: <87liy5ap01.fsf@yoom.home.cworth.org> +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="==-=-==" + +--==-=-== +Content-Type: text/html +

This is an embedded message, with a multipart/alternative part.

+ +--==-=-== +Content-Type: text/plain + This is an embedded message, with a multipart/alternative part. + +--==-=-==-- EOF test_expect_equal_file OUTPUT EXPECTED -- cgit v1.2.3 From 2f2529563002324337d8d3be1e5335d8a1ca7b47 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 6 Mar 2012 18:48:44 +0000 Subject: man: Update raw format documentation --- man/man1/notmuch-show.1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index 7bf1d56..1215f91 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -84,12 +84,17 @@ http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html .TP 4 .BR raw " (default for a single part, see \-\-part)" -For a message, the original, raw content of the email message is -output. Consumers of this format should expect to implement MIME -decoding and similar functions. +For a message or an attached message part, the original, raw content +of the email message is output. Consumers of this format should expect +to implement MIME decoding and similar functions. For a single part (\-\-part) the raw part content is output after -performing any necessary MIME decoding. +performing any necessary MIME decoding. Note that messages with a +simple body still have two parts: part 0 is the whole message and part +1 is the body. + +For a multipart part, the part headers and body (including all child +parts) is output. The raw format must only be used with search terms matching single message. -- cgit v1.2.3 From 6511b08fb81add535b56a183f278b48a45fc9875 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sat, 10 Mar 2012 05:24:50 +0400 Subject: test: remove "Generate some messages" test from raw Before the change, the first subtest in raw format tests just generated messages and checked that they are added successfully. This is not really a raw format test, it is creating of environment required for other subtests to run. The patch removes the first subtest from raw and replaces it with bare add_message calls, similar to how it is done in other tests. TODO: we should check that test environment was created successfully. Currently, many tests do add_message(), notmuch new and other calls without checking the results. We should come up with a general solution for this, i.e. if any command during test initialization fails, all tests should be skipped with appropriate error message. --- test/raw | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/raw b/test/raw index 0171e64..de0b867 100755 --- a/test/raw +++ b/test/raw @@ -3,11 +3,8 @@ test_description='notmuch show --format=raw' . ./test-lib.sh -test_begin_subtest "Generate some messages" -generate_message -generate_message -output=$(NOTMUCH_NEW) -test_expect_equal "$output" "Added 2 new messages to the database." +add_message +add_message test_begin_subtest "Attempt to show multiple raw messages" output=$(notmuch show --format=raw "*" 2>&1) -- cgit v1.2.3 From db97cb5b65dc519d2bd02741a6294fdb7dd74459 Mon Sep 17 00:00:00 2001 From: Dmitry Kurochkin Date: Sat, 10 Mar 2012 05:24:51 +0400 Subject: test: use subtest name for generated message subject by default Before the change, messages generated by generate_message() used "Test message #N" for default subject where N is the generated messages counter. Since message subject is commonly present in expected results, there is a chance of breaking other tests when a new generate_message() call is added. The patch changes default subject value for generated messages to subtest name if it is available. If subtest name is not available (i.e. message is generated during test initialization), the old default value is used (in this case it is fine to have the counter in the subject). Another benefit of this change is a sane default value for subject in generated messages, which would allow to simplify code like: test_begin_subtest "test for a cool feature" add_message [subject]="message for test for a cool feature" --- test/encoding | 2 +- test/search-folder-coherence | 2 +- test/test-lib.sh | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/encoding b/test/encoding index 98abf77..2e1326e 100755 --- a/test/encoding +++ b/test/encoding @@ -9,7 +9,7 @@ output=$(notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize) test_expect_equal "$output" " message{ id:msg-001@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-001 header{ Notmuch Test Suite (2001-01-05) (inbox unread) -Subject: Test message #1 +Subject: Message with text of unknown charset From: Notmuch Test Suite To: Notmuch Test Suite Date: Fri, 05 Jan 2001 15:43:57 +0000 diff --git a/test/search-folder-coherence b/test/search-folder-coherence index f8119cb..3f6ec76 100755 --- a/test/search-folder-coherence +++ b/test/search-folder-coherence @@ -32,7 +32,7 @@ test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Test matches folder:spam" output=$(notmuch search folder:spam) -test_expect_equal "$output" "thread:0000000000000001 2001-01-05 [1/1] Notmuch Test Suite; Test message #1 (inbox unread)" +test_expect_equal "$output" "thread:0000000000000001 2001-01-05 [1/1] Notmuch Test Suite; Single new message (inbox unread)" test_begin_subtest "Remove folder:spam copy of email" rm $dir/spam/$(basename $file_x) diff --git a/test/test-lib.sh b/test/test-lib.sh index 2781506..06aaea2 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -318,7 +318,11 @@ generate_message () fi if [ -z "${template[subject]}" ]; then - template[subject]="Test message #${gen_msg_cnt}" + if [ -n "$test_subtest_name" ]; then + template[subject]="$test_subtest_name" + else + template[subject]="Test message #${gen_msg_cnt}" + fi fi if [ -z "${template[date]}" ]; then -- cgit v1.2.3 From b3e4417897f5b27883513c4714f7e84e377b5f5b Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Sat, 10 Mar 2012 11:05:32 +0000 Subject: cli: Parsing. Allow true/false parameter for boolean options. Allow NOTMUCH_OPT_BOOLEAN to take a true or false parameter. In particular it allows the user to turn off a boolean option with --option=false. --- command-line-arguments.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/command-line-arguments.c b/command-line-arguments.c index e711414..76b185f 100644 --- a/command-line-arguments.c +++ b/command-line-arguments.c @@ -28,6 +28,24 @@ _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, const char *arg_str) { return FALSE; } +static notmuch_bool_t +_process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { + + if (next == 0) { + *((notmuch_bool_t *)arg_desc->output_var) = TRUE; + return TRUE; + } + if (strcmp (arg_str, "false") == 0) { + *((notmuch_bool_t *)arg_desc->output_var) = FALSE; + return TRUE; + } + if (strcmp (arg_str, "true") == 0) { + *((notmuch_bool_t *)arg_desc->output_var) = TRUE; + return TRUE; + } + return FALSE; +} + /* Search for the {pos_arg_index}th position argument, return FALSE if that does not exist. @@ -76,14 +94,15 @@ parse_option (const char *arg, char *endptr; /* Everything but boolean arguments (switches) needs a - * delimiter, and a non-zero length value + * delimiter, and a non-zero length value. Boolean + * arguments may take an optional =true or =false value. */ - - if (try->opt_type != NOTMUCH_OPT_BOOLEAN) { - if (next != '=' && next != ':') return FALSE; - if (value[0] == 0) return FALSE; + if (next != '=' && next != ':' && next != 0) return FALSE; + if (next == 0) { + if (try->opt_type != NOTMUCH_OPT_BOOLEAN) + return FALSE; } else { - if (next != 0) return FALSE; + if (value[0] == 0) return FALSE; } if (try->output_var == NULL) @@ -94,8 +113,7 @@ parse_option (const char *arg, return _process_keyword_arg (try, value); break; case NOTMUCH_OPT_BOOLEAN: - *((notmuch_bool_t *)try->output_var) = TRUE; - return TRUE; + return _process_boolean_arg (try, next, value); break; case NOTMUCH_OPT_INT: *((int *)try->output_var) = strtol (value, &endptr, 10); -- cgit v1.2.3 From 75a05526334b721d719d6c8a8098ff64573e6c1e Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 13 Mar 2012 22:31:30 -0400 Subject: lib: Expose query debug output via an environment variable Allow query debugging to be enabled at run-time by setting the NOTMUCH_DEBUG_QUERY environment variable to a non-empty string. Previously, enabling query debugging required recompiling, but parsed queries are often useful for tracking down bugs in situations where recompiling is inconvenient. --- lib/query.cc | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/query.cc b/lib/query.cc index ab18fbc..1e5e99a 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -64,15 +64,21 @@ _notmuch_doc_id_set_init (void *ctx, notmuch_doc_id_set_t *doc_ids, GArray *arr); +static notmuch_bool_t +_debug_query (void) +{ + char *env = getenv ("NOTMUCH_DEBUG_QUERY"); + return (env && strcmp (env, "") != 0); +} + notmuch_query_t * notmuch_query_create (notmuch_database_t *notmuch, const char *query_string) { notmuch_query_t *query; -#ifdef DEBUG_QUERY - fprintf (stderr, "Query string is:\n%s\n", query_string); -#endif + if (_debug_query ()) + fprintf (stderr, "Query string is:\n%s\n", query_string); query = talloc (NULL, notmuch_query_t); if (unlikely (query == NULL)) @@ -255,9 +261,9 @@ notmuch_query_search_messages (notmuch_query_t *query) break; } -#if DEBUG_QUERY - fprintf (stderr, "Final query is:\n%s\n", final_query.get_description().c_str()); -#endif + if (_debug_query ()) + fprintf (stderr, "Final query is:\n%s\n", + final_query.get_description ().c_str ()); enquire.set_query (final_query); @@ -531,9 +537,9 @@ notmuch_query_count_messages (notmuch_query_t *query) enquire.set_weighting_scheme(Xapian::BoolWeight()); enquire.set_docid_order(Xapian::Enquire::ASCENDING); -#if DEBUG_QUERY - fprintf (stderr, "Final query is:\n%s\n", final_query.get_description().c_str()); -#endif + if (_debug_query ()) + fprintf (stderr, "Final query is:\n%s\n", + final_query.get_description ().c_str ()); enquire.set_query (final_query); -- cgit v1.2.3 From 28367a9bcdda7330a4d0983c3533a7bf5b9cbb8e Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 13 Mar 2012 22:31:31 -0400 Subject: lib: Add exclude query debug output --- lib/query.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/query.cc b/lib/query.cc index 1e5e99a..53d505a 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -261,9 +261,12 @@ notmuch_query_search_messages (notmuch_query_t *query) break; } - if (_debug_query ()) + if (_debug_query ()) { + fprintf (stderr, "Exclude query is:\n%s\n", + exclude_query.get_description ().c_str ()); fprintf (stderr, "Final query is:\n%s\n", final_query.get_description ().c_str ()); + } enquire.set_query (final_query); @@ -537,9 +540,12 @@ notmuch_query_count_messages (notmuch_query_t *query) enquire.set_weighting_scheme(Xapian::BoolWeight()); enquire.set_docid_order(Xapian::Enquire::ASCENDING); - if (_debug_query ()) + if (_debug_query ()) { + fprintf (stderr, "Exclude query is:\n%s\n", + exclude_query.get_description ().c_str ()); fprintf (stderr, "Final query is:\n%s\n", final_query.get_description ().c_str ()); + } enquire.set_query (final_query); -- cgit v1.2.3 From fb36741bf3e81548a6f742c536f910c783756248 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 14 Mar 2012 12:26:52 +0000 Subject: test: the test for the exclude code mistakenly excludes the tag "=" The tests for the exclude code in search and count use the line notmuch config set search.exclude_tags = deleted which actually sets the exclude tags to be "=" and "deleted". Remove the "=" from this line. --- test/count | 2 +- test/search | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/count b/test/count index 976fff1..b97fc06 100755 --- a/test/count +++ b/test/count @@ -38,7 +38,7 @@ test_expect_equal \ "`notmuch count --output=threads ${SEARCH}`" test_begin_subtest "count excluding \"deleted\" messages" -notmuch config set search.exclude_tags = deleted +notmuch config set search.exclude_tags deleted generate_message '[subject]="Not deleted"' generate_message '[subject]="Another not deleted"' generate_message '[subject]="Deleted"' diff --git a/test/search b/test/search index 081f60c..3e3a462 100755 --- a/test/search +++ b/test/search @@ -130,7 +130,7 @@ output=$(notmuch search "bödý" | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)" test_begin_subtest "Exclude \"deleted\" messages from search" -notmuch config set search.exclude_tags = deleted +notmuch config set search.exclude_tags deleted generate_message '[subject]="Not deleted"' generate_message '[subject]="Deleted"' notmuch new > /dev/null -- cgit v1.2.3 From 1351aafac1e8c77e6ee3d2f41ac7445d0e07238d Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 14 Mar 2012 12:26:53 +0000 Subject: test: add tests for message only search This adds three tests for --output=messages searches. One test is for the case when one exclude tag does not occur in the Xapian database. This triggers a Xapian bug in some cases and causes the whole exclusion to fail. The next commit avoids this bug. --- test/search | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/search b/test/search index 3e3a462..17af6a2 100755 --- a/test/search +++ b/test/search @@ -132,13 +132,30 @@ test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; u test_begin_subtest "Exclude \"deleted\" messages from search" notmuch config set search.exclude_tags deleted generate_message '[subject]="Not deleted"' +not_deleted_id=$gen_msg_id generate_message '[subject]="Deleted"' notmuch new > /dev/null notmuch tag +deleted id:$gen_msg_id +deleted_id=$gen_msg_id output=$(notmuch search subject:deleted | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread) thread:XXX 2001-01-05 [0/1] Notmuch Test Suite; Deleted (deleted inbox unread)" +test_begin_subtest "Exclude \"deleted\" messages from message search" +output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "id:$not_deleted_id" + +test_begin_subtest "Exclude \"deleted\" messages from message search (no-exclude)" +output=$(notmuch search --no-exclude --output=messages subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "id:$not_deleted_id +id:$deleted_id" + +test_begin_subtest "Exclude \"deleted\" messages from message search (non-existent exclude-tag)" +notmuch config set search.exclude_tags deleted non_existent_tag +output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize) +test_expect_equal "$output" "id:$not_deleted_id" +notmuch config set search.exclude_tags deleted + test_begin_subtest "Exclude \"deleted\" messages from search, overridden" output=$(notmuch search subject:deleted and tag:deleted | notmuch_search_sanitize) test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Deleted (deleted inbox unread)" -- cgit v1.2.3 From c695534df5830d0681f451bd213e75758b532e31 Mon Sep 17 00:00:00 2001 From: Mark Walters Date: Wed, 14 Mar 2012 12:26:54 +0000 Subject: lib: fix an exclude bug When the exclude tags contain a tag that does not occur anywhere in the Xapian database the exclusion fails. We modify the way the query is constructed to `work around' this. (In fact the new code is cleaner anyway.) It also seems to fix another exclusion failure bug reported by jrollins but we have not yet worked out why it helps in that case. --- lib/query.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/query.cc b/lib/query.cc index 53d505a..68ac1e4 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -219,13 +219,14 @@ notmuch_query_search_messages (notmuch_query_t *query) if (query->exclude_terms) { exclude_query = _notmuch_exclude_tags (query, final_query); - exclude_query = Xapian::Query (Xapian::Query::OP_AND, - exclude_query, final_query); if (query->omit_excluded_messages) final_query = Xapian::Query (Xapian::Query::OP_AND_NOT, final_query, exclude_query); else { + exclude_query = Xapian::Query (Xapian::Query::OP_AND, + exclude_query, final_query); + enquire.set_weighting_scheme (Xapian::BoolWeight()); enquire.set_query (exclude_query); -- cgit v1.2.3 From 0eaf4a21dda9519cc08db5b49126b02c4c6b5682 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 15 Mar 2012 23:13:12 -0400 Subject: emacs: Fix search tab completion in terminals In X, Emacs distinguishes the tab key, which produces a 'tab event; from C-i, which produces a ?\t event. However, in a terminal, these are indistinguishable and only produce a ?\t event. In order to simplify things, Emacs automatically translates from 'tab to ?\t (see "Function key translations" in M-x describe-bindings), so functions only need to be bound to ?\t to work in all situations. Previously, the search tab completion code usedq (kbd ""), which produced the event sequence [tab], which only matched the 'tab event and hence only worked in X. This patch changes it to (kbd "TAB"), which matches the general ?\t event and works in all situations. --- emacs/notmuch.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 99e0c93..f0afa07 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -962,7 +962,7 @@ PROMPT is the string to prompt with." completions))) (t (list string))))))) ;; this was simpler than convincing completing-read to accept spaces: - (define-key keymap (kbd "") 'minibuffer-complete) + (define-key keymap (kbd "TAB") 'minibuffer-complete) (let ((history-delete-duplicates t)) (read-from-minibuffer prompt nil keymap nil 'notmuch-search-history nil nil))))) -- cgit v1.2.3 From c83388819c0c1255f29baa801a7babd5f4f2202f Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sun, 18 Mar 2012 10:15:47 -0300 Subject: NEWS: start section for 0.13 People are making patches for the 0.12 NEWS section for features that won't be there. Let's help them out by adding a new stanza. --- NEWS | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index c34b546..b49e252 100644 --- a/NEWS +++ b/NEWS @@ -1,16 +1,6 @@ -Notmuch 0.12 (2012-xx-xx) +Notmuch 0.13 (2012-xx-xx) ========================= -Command-Line Interface ----------------------- - -Reply to sender - - "notmuch reply" has gained the ability to create a reply template - for replying just to the sender of the message, in addition to reply - to all. The feature is available through the new command line option - --reply-to=(all|sender). - Tag exclusion Tags can be automatically excluded from search results by adding them @@ -29,6 +19,19 @@ Tag exclusion notmuch config set search.exclude_tags deleted spam +Notmuch 0.12 (2012-xx-xx) +========================= + +Command-Line Interface +---------------------- + +Reply to sender + + "notmuch reply" has gained the ability to create a reply template + for replying just to the sender of the message, in addition to reply + to all. The feature is available through the new command line option + --reply-to=(all|sender). + Mail store folder/file ignore A new configuration option, `new.ignore`, lets users specify a -- cgit v1.2.3 From 30172649e8738332edeaf45aa341fe7d20333e8a Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:33 -0600 Subject: test: Add broken test for the new JSON reply format. --- test/multipart | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/multipart b/test/multipart index afc4fc8..e5de5d3 100755 --- a/test/multipart +++ b/test/multipart @@ -612,6 +612,59 @@ Non-text part: text/html EOF test_expect_equal_file OUTPUT EXPECTED +test_begin_subtest "'notmuch reply' to a multipart message with json format" +test_subtest_known_broken +notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT +cat <EXPECTED +{"reply-headers": {"Subject": "Re: Multipart message", + "From": "Notmuch Test Suite ", + "To": "Carl Worth , + cworth@cworth.org", + "In-reply-to": "<87liy5ap00.fsf@yoom.home.cworth.org>", + "References": " <87liy5ap00.fsf@yoom.home.cworth.org>"}, + "original": {"id": "XXXXX", + "match": false, + "excluded": false, + "filename": "YYYYY", + "timestamp": 978709437, + "date_relative": "2001-01-05", + "tags": ["attachment","inbox","signed","unread"], + "headers": {"Subject": "Multipart message", + "From": "Carl Worth ", + "To": "cworth@cworth.org", + "Date": "Fri, + 05 Jan 2001 15:43:57 +0000"}, + "body": [{"id": 1, + "content-type": "multipart/signed", + "content": [{"id": 2, + "content-type": "multipart/mixed", + "content": [{"id": 3, + "content-type": "message/rfc822", + "content": [{"headers": {"Subject": "html message", + "From": "Carl Worth ", + "To": "cworth@cworth.org", + "Date": "Fri, + 05 Jan 2001 15:42:57 +0000"}, + "body": [{"id": 4, + "content-type": "multipart/alternative", + "content": [{"id": 5, + "content-type": "text/html"}, + {"id": 6, + "content-type": "text/plain", + "content": "This is an embedded message, + with a multipart/alternative part.\n"}]}]}]}, + {"id": 7, + "content-type": "text/plain", + "filename": "YYYYY", + "content": "This is a text attachment.\n"}, + {"id": 8, + "content-type": "text/plain", + "content": "And this message is signed.\n\n-Carl\n"}]}, + {"id": 9, + "content-type": "application/pgp-signature"}]}]}} +EOF +test_expect_equal_file OUTPUT EXPECTED + test_begin_subtest "'notmuch show --part' does not corrupt a part with CRLF pair" notmuch show --format=raw --part=3 id:base64-part-with-crlf > crlf.out echo -n -e "\xEF\x0D\x0A" > crlf.expected -- cgit v1.2.3 From 766aebc02c3581aa6e55ce8c94ce5cbba86673c5 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:34 -0600 Subject: reply: Factor out reply creation Factor out the creation of a reply message based on an original message so it can be shared by different reply formats. --- notmuch-reply.c | 104 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 42 deletions(-) diff --git a/notmuch-reply.c b/notmuch-reply.c index 6b244e6..f1478cc 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -505,6 +505,61 @@ guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message return NULL; } +static GMimeMessage * +create_reply_message(void *ctx, + notmuch_config_t *config, + notmuch_message_t *message, + notmuch_bool_t reply_all) +{ + const char *subject, *from_addr = NULL; + const char *in_reply_to, *orig_references, *references; + + /* The 1 means we want headers in a "pretty" order. */ + GMimeMessage *reply = g_mime_message_new (1); + if (reply == NULL) { + fprintf (stderr, "Out of memory\n"); + return NULL; + } + + subject = notmuch_message_get_header (message, "subject"); + if (subject) { + if (strncasecmp (subject, "Re:", 3)) + subject = talloc_asprintf (ctx, "Re: %s", subject); + g_mime_message_set_subject (reply, subject); + } + + from_addr = add_recipients_from_message (reply, config, + message, reply_all); + + if (from_addr == NULL) + from_addr = guess_from_received_header (config, message); + + if (from_addr == NULL) + from_addr = notmuch_config_get_user_primary_email (config); + + from_addr = talloc_asprintf (ctx, "%s <%s>", + notmuch_config_get_user_name (config), + from_addr); + g_mime_object_set_header (GMIME_OBJECT (reply), + "From", from_addr); + + in_reply_to = talloc_asprintf (ctx, "<%s>", + notmuch_message_get_message_id (message)); + + g_mime_object_set_header (GMIME_OBJECT (reply), + "In-Reply-To", in_reply_to); + + orig_references = notmuch_message_get_header (message, "references"); + references = talloc_asprintf (ctx, "%s%s%s", + orig_references ? orig_references : "", + orig_references ? " " : "", + in_reply_to); + g_mime_object_set_header (GMIME_OBJECT (reply), + "References", references); + + return reply; +} + static int notmuch_reply_format_default(void *ctx, notmuch_config_t *config, @@ -515,8 +570,6 @@ notmuch_reply_format_default(void *ctx, GMimeMessage *reply; notmuch_messages_t *messages; notmuch_message_t *message; - const char *subject, *from_addr = NULL; - const char *in_reply_to, *orig_references, *references; const notmuch_show_format_t *format = &format_reply; for (messages = notmuch_query_search_messages (query); @@ -525,49 +578,16 @@ notmuch_reply_format_default(void *ctx, { message = notmuch_messages_get (messages); - /* The 1 means we want headers in a "pretty" order. */ - reply = g_mime_message_new (1); - if (reply == NULL) { - fprintf (stderr, "Out of memory\n"); - return 1; - } + reply = create_reply_message (ctx, config, message, reply_all); - subject = notmuch_message_get_header (message, "subject"); - if (subject) { - if (strncasecmp (subject, "Re:", 3)) - subject = talloc_asprintf (ctx, "Re: %s", subject); - g_mime_message_set_subject (reply, subject); + /* If reply creation failed, we're out of memory, so don't + * bother trying any more messages. + */ + if (!reply) { + notmuch_message_destroy (message); + return 1; } - from_addr = add_recipients_from_message (reply, config, message, - reply_all); - - if (from_addr == NULL) - from_addr = guess_from_received_header (config, message); - - if (from_addr == NULL) - from_addr = notmuch_config_get_user_primary_email (config); - - from_addr = talloc_asprintf (ctx, "%s <%s>", - notmuch_config_get_user_name (config), - from_addr); - g_mime_object_set_header (GMIME_OBJECT (reply), - "From", from_addr); - - in_reply_to = talloc_asprintf (ctx, "<%s>", - notmuch_message_get_message_id (message)); - - g_mime_object_set_header (GMIME_OBJECT (reply), - "In-Reply-To", in_reply_to); - - orig_references = notmuch_message_get_header (message, "references"); - references = talloc_asprintf (ctx, "%s%s%s", - orig_references ? orig_references : "", - orig_references ? " " : "", - in_reply_to); - g_mime_object_set_header (GMIME_OBJECT (reply), - "References", references); - show_reply_headers (reply); g_object_unref (G_OBJECT (reply)); -- cgit v1.2.3 From 06a34f1407a3465724d7950e7179d7f1df130a2b Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:35 -0600 Subject: TODO: Add replying to multiple messages --- devel/TODO | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/devel/TODO b/devel/TODO index 4dda6f4..7b750af 100644 --- a/devel/TODO +++ b/devel/TODO @@ -141,6 +141,14 @@ Simplify notmuch-reply to simply print the headers (we have the original values) rather than calling GMime (which encodes) and adding the confusing gmime-filter-headers.c code (which decodes). +Properly handle replying to multiple messages. Currently, the JSON +reply format only supports a single message, but the default reply +format accepts searches returning multiple messages. The expected +behavior of replying to multiple messages is not obvious, and there +are multiple ideas that might make sense. Some consensus needs to be +reached on this issue, and then both reply formats should be updated +to be consistent. + notmuch library --------------- Add support for custom flag<->tag mappings. In the notmuch -- cgit v1.2.3 From 1904b01b96c1b731afb9649e7b5bceffce901b88 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:36 -0600 Subject: reply: Add a JSON reply format. This new JSON format for replies includes headers generated for a reply message as well as the headers of the original message. Using this data, a client can intelligently create a reply. For example, the emacs client will be able to create replies with quoted HTML parts by parsing the HTML parts. --- notmuch-client.h | 12 +++++++++--- notmuch-reply.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ notmuch-show.c | 29 +++++++++++++++++++++-------- test/multipart | 1 - 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index a220fe4..fa04fa2 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -62,7 +62,7 @@ #define STRINGIFY(s) STRINGIFY_(s) #define STRINGIFY_(s) #s -struct mime_node; +typedef struct mime_node mime_node_t; struct notmuch_show_params; typedef struct notmuch_show_format { @@ -191,6 +191,12 @@ show_message_body (notmuch_message_t *message, notmuch_status_t show_one_part (const char *filename, int part); +void +format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first); + +void +format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply); + char * json_quote_chararray (const void *ctx, const char *str, const size_t len); @@ -288,7 +294,7 @@ debugger_is_active (void); * parts. Message-type parts have one child, multipart-type parts * have multiple children, and leaf parts have zero children. */ -typedef struct mime_node { +struct mime_node { /* The MIME object of this part. This will be a GMimeMessage, * GMimePart, GMimeMultipart, or a subclass of one of these. * @@ -351,7 +357,7 @@ typedef struct mime_node { * number to assign it (or -1 if unknown). */ int next_child; int next_part_num; -} mime_node_t; +}; /* Construct a new MIME node pointing to the root message part of * message. If cryptoctx is non-NULL, it will be used to verify diff --git a/notmuch-reply.c b/notmuch-reply.c index f1478cc..e2b6c25 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -604,6 +604,51 @@ notmuch_reply_format_default(void *ctx, return 0; } +static int +notmuch_reply_format_json(void *ctx, + notmuch_config_t *config, + notmuch_query_t *query, + notmuch_show_params_t *params, + notmuch_bool_t reply_all) +{ + GMimeMessage *reply; + notmuch_messages_t *messages; + notmuch_message_t *message; + mime_node_t *node; + + if (notmuch_query_count_messages (query) != 1) { + fprintf (stderr, "Error: search term did not match precisely one message.\n"); + return 1; + } + + messages = notmuch_query_search_messages (query); + message = notmuch_messages_get (messages); + if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt, + &node) != NOTMUCH_STATUS_SUCCESS) + return 1; + + reply = create_reply_message (ctx, config, message, reply_all); + if (!reply) + return 1; + + /* The headers of the reply message we've created */ + printf ("{\"reply-headers\": "); + format_headers_json (ctx, reply, TRUE); + g_object_unref (G_OBJECT (reply)); + reply = NULL; + + /* Start the original */ + printf (", \"original\": "); + + format_part_json (ctx, node, TRUE); + + /* End */ + printf ("}\n"); + notmuch_message_destroy (message); + + return 0; +} + /* This format is currently tuned for a git send-email --notmuch hook */ static int notmuch_reply_format_headers_only(void *ctx, @@ -666,6 +711,7 @@ notmuch_reply_format_headers_only(void *ctx, enum { FORMAT_DEFAULT, + FORMAT_JSON, FORMAT_HEADERS_ONLY, }; @@ -685,6 +731,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) notmuch_opt_desc_t options[] = { { NOTMUCH_OPT_KEYWORD, &format, "format", 'f', (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT }, + { "json", FORMAT_JSON }, { "headers-only", FORMAT_HEADERS_ONLY }, { 0, 0 } } }, { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r', @@ -703,6 +750,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[]) if (format == FORMAT_HEADERS_ONLY) reply_format_func = notmuch_reply_format_headers_only; + else if (format == FORMAT_JSON) + reply_format_func = notmuch_reply_format_json; else reply_format_func = notmuch_reply_format_default; diff --git a/notmuch-show.c b/notmuch-show.c index a7463dc..ff9d427 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -200,8 +200,8 @@ _is_from_line (const char *line) return 0; } -static void -format_headers_json (const void *ctx, GMimeMessage *message) +void +format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply) { void *local = talloc_new (ctx); InternetAddressList *recipients; @@ -225,9 +225,22 @@ format_headers_json (const void *ctx, GMimeMessage *message) printf (", %s: %s", json_quote_str (local, "Cc"), json_quote_str (local, recipients_string)); - printf (", %s: %s}", - json_quote_str (local, "Date"), - json_quote_str (local, g_mime_message_get_date_as_string (message))); + + if (reply) { + printf (", %s: %s", + json_quote_str (local, "In-reply-to"), + json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to"))); + + printf (", %s: %s", + json_quote_str (local, "References"), + json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "References"))); + } else { + printf (", %s: %s", + json_quote_str (local, "Date"), + json_quote_str (local, g_mime_message_get_date_as_string (message))); + } + + printf ("}"); talloc_free (local); } @@ -538,7 +551,7 @@ format_part_text (const void *ctx, mime_node_t *node, return NOTMUCH_STATUS_SUCCESS; } -static void +void format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) { /* Any changes to the JSON format should be reflected in the file @@ -549,7 +562,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) format_message_json (ctx, node->envelope_file); printf ("\"headers\": "); - format_headers_json (ctx, GMIME_MESSAGE (node->part)); + format_headers_json (ctx, GMIME_MESSAGE (node->part), FALSE); printf (", \"body\": ["); format_part_json (ctx, mime_node_child (node, 0), first); @@ -623,7 +636,7 @@ format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first) } else if (GMIME_IS_MESSAGE (node->part)) { printf (", \"content\": [{"); printf ("\"headers\": "); - format_headers_json (local, GMIME_MESSAGE (node->part)); + format_headers_json (local, GMIME_MESSAGE (node->part), FALSE); printf (", \"body\": ["); terminator = "]}]"; diff --git a/test/multipart b/test/multipart index e5de5d3..72d3927 100755 --- a/test/multipart +++ b/test/multipart @@ -613,7 +613,6 @@ EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "'notmuch reply' to a multipart message with json format" -test_subtest_known_broken notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT cat <EXPECTED {"reply-headers": {"Subject": "Re: Multipart message", -- cgit v1.2.3 From 5abc9c1097a54e0e8b62e468d51728edb26b9101 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:37 -0600 Subject: schemata: Add documentation for JSON reply format. --- devel/schemata | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/devel/schemata b/devel/schemata index 24ad775..728a46f 100644 --- a/devel/schemata +++ b/devel/schemata @@ -77,8 +77,9 @@ part = { content?: string } -# The headers of a message (format_headers_json with raw headers) or -# a part (format_headers_message_part_json with pretty-printed headers) +# The headers of a message (format_headers_json with raw headers +# and reply = FALSE) or a part (format_headers_message_part_json +# with pretty-printed headers) headers = { Subject: string, From: string, @@ -136,3 +137,25 @@ thread = { # matched and unmatched subject: string } + +notmuch reply schema +-------------------- + +reply = { + # The headers of the constructed reply (format_headers_json with + # raw headers and reply = TRUE) + reply-headers: reply_headers, + + # As in the show format (format_part_json) + original: message +} + +reply_headers = { + Subject: string, + From: string, + To?: string, + Cc?: string, + Bcc?: string, + In-reply-to: string, + References: string +} -- cgit v1.2.3 From 71855b72f702e8751c35f71c787a00d64a77b663 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:38 -0600 Subject: man: Update notmuch-reply man page for JSON format. --- man/man1/notmuch-reply.1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index deb3ae1..ec8da1f 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -37,12 +37,17 @@ Supported options for include .RS .TP 4 -.BR \-\-format= ( default | headers\-only ) +.BR \-\-format= ( default | json | headers\-only ) .RS .TP 4 .BR default Includes subject and quoted message body. .TP +.BR json +Produces JSON output containing headers for a reply message and the +contents of the original message. This output can be used by a client +to create a reply message intelligently. +.TP .BR headers\-only Only produces In\-Reply\-To, References, To, Cc, and Bcc headers. .RE @@ -73,7 +78,8 @@ with a search string matching a single message, (such as id:), but it can be useful to reply to several messages at once. For example, when a series of patches are sent in a single thread, replying to the entire thread allows for the reply to comment -on issue found in multiple patches. +on issues found in multiple patches. The default format supports +replying to multiple messages at once, but the JSON format does not. .RE .RE -- cgit v1.2.3 From 92b48c8c98e29ee54ce603931b3abf4c7b521840 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:39 -0600 Subject: man: Add --decrypt to reply flags --- man/man1/notmuch-reply.1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index ec8da1f..7ee3ceb 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -68,6 +68,16 @@ values from the first that contains something other than only the user's addresses. .RE .RE +.RS +.TP 4 +.B \-\-decrypt + +Decrypt any MIME encrypted parts found in the selected content +(ie. "multipart/encrypted" parts). Status of the decryption will be +reported (currently only supported with --format=json) and the +multipart/encrypted part will be replaced by the decrypted +content. +.RE See \fBnotmuch-search-terms\fR(7) for details of the supported syntax for . -- cgit v1.2.3 From 950789f3c330d80e083c788777135494dd1bc6d4 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:40 -0600 Subject: emacs: Factor out useful functions into notmuch-lib Move a few functions related to handling multipart/alternative parts into notmuch-lib.el, so they can be used by future reply code. --- emacs/notmuch-lib.el | 33 +++++++++++++++++++++++++++++++++ emacs/notmuch-show.el | 24 ++---------------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index d315f76..7e3f110 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -21,6 +21,8 @@ ;; This is an part of an emacs-based interface to the notmuch mail system. +(eval-when-compile (require 'cl)) + (defvar notmuch-command "notmuch" "Command to run the notmuch binary.") @@ -173,6 +175,37 @@ the user hasn't set this variable with the old or new value." (list 'when (< emacs-major-version 23) form)) +(defun notmuch-split-content-type (content-type) + "Split content/type into 'content' and 'type'" + (split-string content-type "/")) + +(defun notmuch-match-content-type (t1 t2) + "Return t if t1 and t2 are matching content types, taking wildcards into account" + (let ((st1 (notmuch-split-content-type t1)) + (st2 (notmuch-split-content-type t2))) + (if (or (string= (cadr st1) "*") + (string= (cadr st2) "*")) + (string= (car st1) (car st2)) + (string= t1 t2)))) + +(defvar notmuch-multipart/alternative-discouraged + '( + ;; Avoid HTML parts. + "text/html" + ;; multipart/related usually contain a text/html part and some associated graphics. + "multipart/related" + )) + +(defun notmuch-multipart/alternative-choose (types) + "Return a list of preferred types from the given list of types" + ;; Based on `mm-preferred-alternative-precedence'. + (let ((seq types)) + (dolist (pref (reverse notmuch-multipart/alternative-discouraged)) + (dolist (elem (copy-sequence seq)) + (when (string-match pref elem) + (setq seq (nconc (delete elem seq) (list elem)))))) + seq)) + ;; Compatibility functions for versions of emacs before emacs 23. ;; ;; Both functions here were copied from emacs 23 with the following copyright: diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index 4a60631..ed938bf 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -542,30 +542,13 @@ current buffer, if possible." (mm-display-part handle) t)))))) -(defvar notmuch-show-multipart/alternative-discouraged - '( - ;; Avoid HTML parts. - "text/html" - ;; multipart/related usually contain a text/html part and some associated graphics. - "multipart/related" - )) - (defun notmuch-show-multipart/*-to-list (part) (mapcar (lambda (inner-part) (plist-get inner-part :content-type)) (plist-get part :content))) -(defun notmuch-show-multipart/alternative-choose (types) - ;; Based on `mm-preferred-alternative-precedence'. - (let ((seq types)) - (dolist (pref (reverse notmuch-show-multipart/alternative-discouraged)) - (dolist (elem (copy-sequence seq)) - (when (string-match pref elem) - (setq seq (nconc (delete elem seq) (list elem)))))) - seq)) - (defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth declared-type) (notmuch-show-insert-part-header nth declared-type content-type nil) - (let ((chosen-type (car (notmuch-show-multipart/alternative-choose (notmuch-show-multipart/*-to-list part)))) + (let ((chosen-type (car (notmuch-multipart/alternative-choose (notmuch-show-multipart/*-to-list part)))) (inner-parts (plist-get part :content)) (start (point))) ;; This inserts all parts of the chosen type rather than just one, @@ -808,9 +791,6 @@ current buffer, if possible." ;; Functions for determining how to handle MIME parts. -(defun notmuch-show-split-content-type (content-type) - (split-string content-type "/")) - (defun notmuch-show-handlers-for (content-type) "Return a list of content handlers for a part of type CONTENT-TYPE." (let (result) @@ -821,7 +801,7 @@ current buffer, if possible." (list (intern (concat "notmuch-show-insert-part-*/*")) (intern (concat "notmuch-show-insert-part-" - (car (notmuch-show-split-content-type content-type)) + (car (notmuch-split-content-type content-type)) "/*")) (intern (concat "notmuch-show-insert-part-" content-type)))) result)) -- cgit v1.2.3 From 8420ba10358dcc1d0d306dd1298f07fae2150e11 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:41 -0600 Subject: test: Add broken tests for new emacs reply functionality Add tests for creating nice replies to multipart messages, including those with HTML parts. These tests are expected to fail for now. --- test/emacs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/test/emacs b/test/emacs index 29a489c..01afdb6 100755 --- a/test/emacs +++ b/test/emacs @@ -273,6 +273,103 @@ On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite w EOF test_expect_equal_file OUTPUT EXPECTED +test_begin_subtest "Reply within emacs to a multipart/mixed message" +test_subtest_known_broken +test_emacs '(notmuch-show "id:20091118002059.067214ed@hikari") + (notmuch-show-reply) + (test-output)' +cat <EXPECTED +From: Notmuch Test Suite +To: Adrian Perez de Castro , notmuch@notmuchmail.org +Subject: Re: [notmuch] Introducing myself +In-Reply-To: <20091118002059.067214ed@hikari> +Fcc: ${MAIL_DIR}/sent +--text follows this line-- +Adrian Perez de Castro writes: + +> Hello to all, +> +> I have just heard about Not Much today in some random Linux-related news +> site (LWN?), my name is Adrian Perez and I work as systems administrator +> (although I can do some code as well :P). I have always thought that the +> ideas behind Sup were great, but after some time using it, I got tired of +> the oddities that it has. I also do not like doing things like having to +> install Ruby just for reading and sorting mails. Some time ago I thought +> about doing something like Not Much and in fact I played a bit with the +> Python+Xapian and the Python+Whoosh combinations, because I find relaxing +> to code things in Python when I am not working and also it is installed +> by default on most distribution. I got to have some mailboxes indexed and +> basic searching working a couple of months ago. Lately I have been very +> busy and had no time for coding, and them... boom! Not Much appears -- and +> it is almost exactly what I was trying to do, but faster. I have been +> playing a bit with Not Much today, and I think it has potential. +> +> Also, I would like to share one idea I had in mind, that you might find +> interesting: One thing I have found very annoying is having to re-tag my +> mail when the indexes get b0rked (it happened a couple of times to me while +> using Sup), so I was planning to mails as read/unread and adding the tags +> not just to the index, but to the mail text itself, e.g. by adding a +> "X-Tags" header field or by reusing the "Keywords" one. This way, the index +> could be totally recreated by re-reading the mail directories, and this +> would also allow to a tools like OfflineIMAP [1] to get the mails into a +> local maildir, tagging and indexing the mails with the e-mail reader and +> then syncing back the messages with the "X-Tags" header to the IMAP server. +> This would allow to use the mail reader from a different computer and still +> have everything tagged finely. +> +> Best regards, +> +> +> --- +> [1] http://software.complete.org/software/projects/show/offlineimap +> +> -- +> Adrian Perez de Castro +> Igalia - Free Software Engineering +> _______________________________________________ +> notmuch mailing list +> notmuch@notmuchmail.org +> http://notmuchmail.org/mailman/listinfo/notmuch +EOF +test_expect_equal_file OUTPUT EXPECTED + +test_begin_subtest "Reply within emacs to a multipart/alternative message" +test_subtest_known_broken +test_emacs '(notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com") + (notmuch-show-reply) + (test-output)' +cat <EXPECTED +From: Notmuch Test Suite +To: Alex Botero-Lowry , notmuch@notmuchmail.org +Subject: Re: [notmuch] preliminary FreeBSD support +In-Reply-To: +Fcc: ${MAIL_DIR}/sent +--text follows this line-- +Alex Botero-Lowry writes: + +> I saw the announcement this morning, and was very excited, as I had been +> hoping sup would be turned into a library, +> since I like the concept more than the UI (I'd rather an emacs interface). +> +> I did a preliminary compile which worked out fine, but +> sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns -1 on +> FreeBSD, so notmuch_config_open segfaulted. +> +> Attached is a patch that supplies a default buffer size of 64 in cases where +> -1 is returned. +> +> http://www.opengroup.org/austin/docs/austin_328.txt - seems to indicate this +> is acceptable behavior, +> and http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.htmlspecifically +> uses 64 as the +> buffer size. +> _______________________________________________ +> notmuch mailing list +> notmuch@notmuchmail.org +> http://notmuchmail.org/mailman/listinfo/notmuch +EOF +test_expect_equal_file OUTPUT EXPECTED + test_begin_subtest "Quote MML tags in reply" message_id='test-emacs-mml-quoting@message.id' add_message [id]="$message_id" \ -- cgit v1.2.3 From 650123510cfa64caf6b20f5239f43433fa6f2941 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:42 -0600 Subject: emacs: Use the new JSON reply format and message-cite-original Use the new JSON reply format to create replies in emacs. Quote HTML parts nicely by using mm-display-part to turn them into displayable text, then quoting them with message-cite-original. This is very useful for users who regularly receive HTML-only email. Use message-mode's message-cite-original function to create the quoted body for reply messages. In order to make this act like the existing notmuch defaults, you will need to set the following in your emacs configuration: message-citation-line-format "On %a, %d %b %Y, %f wrote:" message-citation-line-function 'message-insert-formatted-citation-line The tests have been updated to reflect the (ugly) emacs default. --- emacs/notmuch-lib.el | 30 ++++++++++++ emacs/notmuch-mua.el | 124 +++++++++++++++++++++++++++++++++----------------- emacs/notmuch-show.el | 31 +++---------- test/emacs | 8 ++-- 4 files changed, 123 insertions(+), 70 deletions(-) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 7e3f110..c146748 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -206,6 +206,36 @@ the user hasn't set this variable with the old or new value." (setq seq (nconc (delete elem seq) (list elem)))))) seq)) +(defun notmuch-parts-filter-by-type (parts type) + "Given a list of message parts, return a list containing the ones matching +the given type." + (remove-if-not + (lambda (part) (notmuch-match-content-type (plist-get part :content-type) type)) + parts)) + +;; Helper for parts which are generally not included in the default +;; JSON output. +(defun notmuch-get-bodypart-internal (message-id part-number process-crypto) + (let ((args '("show" "--format=raw")) + (part-arg (format "--part=%s" part-number))) + (setq args (append args (list part-arg))) + (if process-crypto + (setq args (append args '("--decrypt")))) + (setq args (append args (list message-id))) + (with-temp-buffer + (let ((coding-system-for-read 'no-conversion)) + (progn + (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args)) + (buffer-string)))))) + +(defun notmuch-get-bodypart-content (msg part nth process-crypto) + (or (plist-get part :content) + (notmuch-get-bodypart-internal (concat "id:" (plist-get msg :id)) nth process-crypto))) + +(defun notmuch-plist-to-alist (plist) + (loop for (key value . rest) on plist by #'cddr + collect (cons (substring (symbol-name key) 1) value))) + ;; Compatibility functions for versions of emacs before emacs 23. ;; ;; Both functions here were copied from emacs 23 with the following copyright: diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index 13244eb..6aae3a0 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -19,11 +19,15 @@ ;; ;; Authors: David Edmondson +(require 'json) (require 'message) +(require 'format-spec) (require 'notmuch-lib) (require 'notmuch-address) +(eval-when-compile (require 'cl)) + ;; (defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook) @@ -72,54 +76,92 @@ list." (push header message-hidden-headers))) notmuch-mua-hidden-headers)) +(defun notmuch-mua-get-quotable-parts (parts) + (loop for part in parts + if (notmuch-match-content-type (plist-get part :content-type) "multipart/alternative") + collect (let* ((subparts (plist-get part :content)) + (types (mapcar (lambda (part) (plist-get part :content-type)) subparts)) + (chosen-type (car (notmuch-multipart/alternative-choose types)))) + (loop for part in (reverse subparts) + if (notmuch-match-content-type (plist-get part :content-type) chosen-type) + return part)) + else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*") + append (notmuch-mua-get-quotable-parts (plist-get part :content)) + else if (notmuch-match-content-type (plist-get part :content-type) "text/*") + collect part)) + (defun notmuch-mua-reply (query-string &optional sender reply-all) - (let (headers - body - (args '("reply"))) - (if notmuch-show-process-crypto - (setq args (append args '("--decrypt")))) + (let ((args '("reply" "--format=json")) + reply + original) + (when notmuch-show-process-crypto + (setq args (append args '("--decrypt")))) + (if reply-all (setq args (append args '("--reply-to=all"))) (setq args (append args '("--reply-to=sender")))) (setq args (append args (list query-string))) - ;; This make assumptions about the output of `notmuch reply', but - ;; really only that the headers come first followed by a blank - ;; line and then the body. + + ;; Get the reply object as JSON, and parse it into an elisp object. (with-temp-buffer (apply 'call-process (append (list notmuch-command nil (list t t) nil) args)) (goto-char (point-min)) - (if (re-search-forward "^$" nil t) - (save-excursion - (save-restriction - (narrow-to-region (point-min) (point)) - (goto-char (point-min)) - (setq headers (mail-header-extract))))) - (forward-line 1) - ;; Original message may contain (malicious) MML tags. We must - ;; properly quote them in the reply. - (mml-quote-region (point) (point-max)) - (setq body (buffer-substring (point) (point-max)))) - ;; If sender is non-nil, set the From: header to its value. - (when sender - (mail-header-set 'from sender headers)) - (let - ;; Overlay the composition window on that being used to read - ;; the original message. - ((same-window-regexps '("\\*mail .*"))) - (notmuch-mua-mail (mail-header 'to headers) - (mail-header 'subject headers) - (message-headers-to-generate headers t '(to subject)))) - ;; insert the message body - but put it in front of the signature - ;; if one is present - (goto-char (point-max)) - (if (re-search-backward message-signature-separator nil t) + (let ((json-object-type 'plist) + (json-array-type 'list) + (json-false 'nil)) + (setq reply (json-read)))) + + ;; Extract the original message to simplify the following code. + (setq original (plist-get reply :original)) + + ;; Extract the headers of both the reply and the original message. + (let* ((original-headers (plist-get original :headers)) + (reply-headers (plist-get reply :reply-headers))) + + ;; If sender is non-nil, set the From: header to its value. + (when sender + (plist-put reply-headers :From sender)) + (let + ;; Overlay the composition window on that being used to read + ;; the original message. + ((same-window-regexps '("\\*mail .*"))) + (notmuch-mua-mail (plist-get reply-headers :To) + (plist-get reply-headers :Subject) + (notmuch-plist-to-alist reply-headers))) + ;; Insert the message body - but put it in front of the signature + ;; if one is present + (goto-char (point-max)) + (if (re-search-backward message-signature-separator nil t) (forward-line -1) - (goto-char (point-max))) - (insert body) - (push-mark)) - (set-buffer-modified-p nil) - - (message-goto-body)) + (goto-char (point-max))) + + (let ((from (plist-get original-headers :From)) + (date (plist-get original-headers :Date)) + (start (point))) + + ;; message-cite-original constructs a citation line based on the From and Date + ;; headers of the original message, which are assumed to be in the buffer. + (insert "From: " from "\n") + (insert "Date: " date "\n\n") + + ;; Get the parts of the original message that should be quoted; this includes + ;; all the text parts, except the non-preferred ones in a multipart/alternative. + (let ((quotable-parts (notmuch-mua-get-quotable-parts (plist-get original :body)))) + (mapc (lambda (part) + (insert (notmuch-get-bodypart-content original part + (plist-get part :id) + notmuch-show-process-crypto))) + quotable-parts)) + + (set-mark (point)) + (goto-char start) + ;; Quote the original message according to the user's configured style. + (message-cite-original)))) + + (goto-char (point-max)) + (push-mark) + (message-goto-body) + (set-buffer-modified-p nil)) (defun notmuch-mua-forward-message () (message-forward) @@ -145,7 +187,7 @@ OTHER-ARGS are passed through to `message-mail'." (when (not (string= "" user-agent)) (push (cons "User-Agent" user-agent) other-headers)))) - (unless (mail-header 'from other-headers) + (unless (mail-header 'From other-headers) (push (cons "From" (concat (notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers)) @@ -208,7 +250,7 @@ the From: address first." (interactive "P") (let ((other-headers (when (or prompt-for-sender notmuch-always-prompt-for-sender) - (list (cons 'from (notmuch-mua-prompt-for-sender)))))) + (list (cons 'From (notmuch-mua-prompt-for-sender)))))) (notmuch-mua-mail nil nil other-headers))) (defun notmuch-mua-new-forward-message (&optional prompt-for-sender) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index ed938bf..0cd7d82 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -488,7 +488,7 @@ message at DEPTH in the current thread." (setq notmuch-show-process-crypto ,process-crypto) ;; Always acquires the part via `notmuch part', even if it is ;; available in the JSON output. - (insert (notmuch-show-get-bodypart-internal ,message-id ,nth)) + (insert (notmuch-get-bodypart-internal ,message-id ,nth notmuch-show-process-crypto)) ,@body)))) (defun notmuch-show-save-part (message-id nth &optional filename content-type) @@ -536,7 +536,7 @@ current buffer, if possible." ;; test whether we are able to inline it (which includes both ;; capability and suitability tests). (when (mm-inlined-p handle) - (insert (notmuch-show-get-bodypart-content msg part nth)) + (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto)) (when (mm-inlinable-p handle) (set-buffer display-buffer) (mm-display-part handle) @@ -613,8 +613,8 @@ current buffer, if possible." ;; times (hundreds!), which results in many calls to ;; `notmuch part'. (unless content - (setq content (notmuch-show-get-bodypart-internal (concat "id:" message-id) - part-number)) + (setq content (notmuch-get-bodypart-internal (concat "id:" message-id) + part-number notmuch-show-process-crypto)) (with-current-buffer w3m-current-buffer (notmuch-show-w3m-cid-store-internal url message-id @@ -734,7 +734,7 @@ current buffer, if possible." ;; insert a header to make this clear. (if (> nth 1) (notmuch-show-insert-part-header nth declared-type content-type (plist-get part :filename))) - (insert (notmuch-show-get-bodypart-content msg part nth)) + (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto)) (save-excursion (save-restriction (narrow-to-region start (point-max)) @@ -744,7 +744,7 @@ current buffer, if possible." (defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth declared-type) (notmuch-show-insert-part-header nth declared-type content-type (plist-get part :filename)) (insert (with-temp-buffer - (insert (notmuch-show-get-bodypart-content msg part nth)) + (insert (notmuch-get-bodypart-content msg part nth notmuch-show-process-crypto)) (goto-char (point-min)) (let ((file (make-temp-file "notmuch-ical")) result) @@ -806,25 +806,6 @@ current buffer, if possible." (intern (concat "notmuch-show-insert-part-" content-type)))) result)) -;; Helper for parts which are generally not included in the default -;; JSON output. -(defun notmuch-show-get-bodypart-internal (message-id part-number) - (let ((args '("show" "--format=raw")) - (part-arg (format "--part=%s" part-number))) - (setq args (append args (list part-arg))) - (if notmuch-show-process-crypto - (setq args (append args '("--decrypt")))) - (setq args (append args (list message-id))) - (with-temp-buffer - (let ((coding-system-for-read 'no-conversion)) - (progn - (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args)) - (buffer-string)))))) - -(defun notmuch-show-get-bodypart-content (msg part nth) - (or (plist-get part :content) - (notmuch-show-get-bodypart-internal (concat "id:" (plist-get msg :id)) nth))) - ;; (defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth declared-type) diff --git a/test/emacs b/test/emacs index 01afdb6..8a28705 100755 --- a/test/emacs +++ b/test/emacs @@ -268,13 +268,13 @@ Subject: Re: Testing message sent via SMTP In-Reply-To: Fcc: ${MAIL_DIR}/sent --text follows this line-- -On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite wrote: +Notmuch Test Suite writes: + > This is a test that messages are sent via SMTP EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Reply within emacs to a multipart/mixed message" -test_subtest_known_broken test_emacs '(notmuch-show "id:20091118002059.067214ed@hikari") (notmuch-show-reply) (test-output)' @@ -334,7 +334,6 @@ EOF test_expect_equal_file OUTPUT EXPECTED test_begin_subtest "Reply within emacs to a multipart/alternative message" -test_subtest_known_broken test_emacs '(notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com") (notmuch-show-reply) (test-output)' @@ -385,7 +384,8 @@ Subject: Re: Quote MML tags in reply In-Reply-To: Fcc: ${MAIL_DIR}/sent --text follows this line-- -On Fri, 05 Jan 2001 15:43:57 +0000, Notmuch Test Suite wrote: +Notmuch Test Suite writes: + > <#!part disposition=inline> EOF test_expect_equal_file OUTPUT EXPECTED -- cgit v1.2.3 From eb82b4551ad0c5e808a677d886f58ba2c41f970d Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Sun, 18 Mar 2012 10:32:43 -0600 Subject: NEWS: news for reply enhancements --- NEWS | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/NEWS b/NEWS index b49e252..5cfe222 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,28 @@ Notmuch 0.13 (2012-xx-xx) ========================= +Command-Line Interface +---------------------- + +Reply to sender + + "notmuch reply" has gained the ability to create a reply template + for replying just to the sender of the message, in addition to reply + to all. The feature is available through the new command line option + --reply-to=(all|sender). + +JSON reply format + + "notmuch reply" can now produce JSON output that contains the headers + for a reply message and full information about the original message + begin replied to. This allows MUAs to create replies intelligtently. + For example, an MUA that can parse HTML might quote HTML parts. + + Calling notmuch reply with --format=json imposes the restriction that + only a single message is returned by the search, as replying to + multiple messages does not have a well-defined behavior. The default + retains its current behavior for multiple message replies. + Tag exclusion Tags can be automatically excluded from search results by adding them @@ -19,6 +41,17 @@ Tag exclusion notmuch config set search.exclude_tags deleted spam +Emacs Interface +--------------- + +Reply improvement using the JSON format + + Emacs now uses the JSON reply format to create replies. It obeys + the customization variables message-citation-line-format and + message-citation-line-function when creating the first line of the + reply body, and it will quote HTML parts if no text/plain parts are + available. + Notmuch 0.12 (2012-xx-xx) ========================= -- cgit v1.2.3 From 7fb0eb3293037b338c808da0d53ee4452b65993a Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 19 Mar 2012 22:15:05 -0300 Subject: NEWS: set release date for 0.12 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 09c5d9c..bfef158 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Notmuch 0.12 (2012-xx-xx) +Notmuch 0.12 (2012-03-20) ========================= Command-Line Interface -- cgit v1.2.3 From 686ed4c6ad9f601af76bcd81856c0a62c63122c7 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 19 Mar 2012 22:25:05 -0300 Subject: NEWS: update discussion of GMime 2.6 version requirements. Be more specific than "Current GMime 2.6". Also explain why we need >=2.6.7 --- NEWS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index bfef158..f179106 100644 --- a/NEWS +++ b/NEWS @@ -134,8 +134,10 @@ Build fixes Compatibility with GMime 2.6 It is now possible to build notmuch against both GMime 2.4 and 2.6. - However, a bug in current GMime 2.6 causes notmuch not to report - signatures where the signer key is unavailable (GNOME bug 668085). + However, a bug in GMime 2.6 before 2.6.5 causes notmuch not to + report signatures where the signer key is unavailable (GNOME bug + 668085). For compatibility with GMime 2.4's tolerance of "From " + headers we require GMime 2.6 >= 2.6.7. Notmuch 0.11.1 (2012-02-03) =========================== -- cgit v1.2.3 From c302bfa2f63fc1e59df6f842fe65d4d6ea258a33 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 19 Mar 2012 22:28:17 -0300 Subject: update version to 0.12 There may be a few NEWS changes after this, but no code (hopefully). --- bindings/python/notmuch/version.py | 2 +- man/man1/notmuch-config.1 | 2 +- man/man1/notmuch-count.1 | 2 +- man/man1/notmuch-dump.1 | 2 +- man/man1/notmuch-new.1 | 2 +- man/man1/notmuch-reply.1 | 2 +- man/man1/notmuch-restore.1 | 2 +- man/man1/notmuch-search.1 | 2 +- man/man1/notmuch-show.1 | 2 +- man/man1/notmuch-tag.1 | 2 +- man/man1/notmuch.1 | 2 +- man/man5/notmuch-hooks.5 | 2 +- man/man7/notmuch-search-terms.7 | 2 +- version | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index 8f7eb87..24e1d4c 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,2 +1,2 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.12~rc2' +__VERSION__ = '0.12' diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1 index fb023b8..a746895 100644 --- a/man/man1/notmuch-config.1 +++ b/man/man1/notmuch-config.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-CONFIG 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-CONFIG 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-config \- Access notmuch configuration file. .SH SYNOPSIS diff --git a/man/man1/notmuch-count.1 b/man/man1/notmuch-count.1 index 383252e..8de4345 100644 --- a/man/man1/notmuch-count.1 +++ b/man/man1/notmuch-count.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-COUNT 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-COUNT 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-count \- Count messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-dump.1 b/man/man1/notmuch-dump.1 index 7cfe08e..f479e8b 100644 --- a/man/man1/notmuch-dump.1 +++ b/man/man1/notmuch-dump.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-DUMP 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-DUMP 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-dump \- Creates a plain-text dump of the tags of each message. diff --git a/man/man1/notmuch-new.1 b/man/man1/notmuch-new.1 index 1158bb2..613658d 100644 --- a/man/man1/notmuch-new.1 +++ b/man/man1/notmuch-new.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-NEW 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-NEW 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-new \- Incorporate new mail into the notmuch database. .SH SYNOPSIS diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1 index deb3ae1..bd95b5f 100644 --- a/man/man1/notmuch-reply.1 +++ b/man/man1/notmuch-reply.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-REPLY 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-REPLY 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-reply \- Constructs a reply template for a set of messages. diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1 index 333f488..db0b697 100644 --- a/man/man1/notmuch-restore.1 +++ b/man/man1/notmuch-restore.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-RESTORE 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-RESTORE 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-restore \- Restores the tags from the given file (see notmuch dump). diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1 index 2cf830a..bf17220 100644 --- a/man/man1/notmuch-search.1 +++ b/man/man1/notmuch-search.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SEARCH 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-SEARCH 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-search \- Search for messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1 index 48c7e0b..d69834a 100644 --- a/man/man1/notmuch-show.1 +++ b/man/man1/notmuch-show.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SHOW 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-SHOW 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-show \- Show messages matching the given search terms. .SH SYNOPSIS diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1 index 8a010e4..aa4546e 100644 --- a/man/man1/notmuch-tag.1 +++ b/man/man1/notmuch-tag.1 @@ -1,4 +1,4 @@ -.TH NOTMUCH-TAG 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-TAG 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-tag \- Add/remove tags for all messages matching the search terms. diff --git a/man/man1/notmuch.1 b/man/man1/notmuch.1 index 3984314..2afcc77 100644 --- a/man/man1/notmuch.1 +++ b/man/man1/notmuch.1 @@ -16,7 +16,7 @@ .\" along with this program. If not, see http://www.gnu.org/licenses/ . .\" .\" Author: Carl Worth -.TH NOTMUCH 1 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH 1 2012-03-19 "Notmuch 0.12" .SH NAME notmuch \- thread-based email index, search, and tagging .SH SYNOPSIS diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5 index 2397c7e..9662bde 100644 --- a/man/man5/notmuch-hooks.5 +++ b/man/man5/notmuch-hooks.5 @@ -1,4 +1,4 @@ -.TH NOTMUCH-HOOKS 5 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-HOOKS 5 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-hooks \- hooks for notmuch diff --git a/man/man7/notmuch-search-terms.7 b/man/man7/notmuch-search-terms.7 index a2dde70..37ba9bb 100644 --- a/man/man7/notmuch-search-terms.7 +++ b/man/man7/notmuch-search-terms.7 @@ -1,4 +1,4 @@ -.TH NOTMUCH-SEARCH-TERMS 7 2012-03-18 "Notmuch 0.12~rc2" +.TH NOTMUCH-SEARCH-TERMS 7 2012-03-19 "Notmuch 0.12" .SH NAME notmuch-search-terms \- Syntax for notmuch queries diff --git a/version b/version index d885035..c43e105 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.12~rc2 +0.12 -- cgit v1.2.3 From 5b6a91849c0d8aff1ae4fd147c0a8510d69fcc83 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 19 Mar 2012 22:32:23 -0300 Subject: NEWS: add news item for help = man page As we discovered recently on the list, this really is new for 0.12 --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index f179106..5c54735 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,12 @@ Mail store folder/file ignore NOTE: *Every* file/directory that goes by one of those names will be ignored, independent of its depth/location in the mail store. +Unified help and manual pages + + The notmuch help command now runs man for the appropriate page. If + you install notmuch somewhere "unusual", you may need to update + MANPATH. + Manual page for notmuch configuration options The notmuch CLI configuration file options are now documented in the -- cgit v1.2.3 From 82fd8c1a83518c49fa7c7b87c6595156106e1f79 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Mon, 19 Mar 2012 22:43:50 -0300 Subject: debian: add changelog stanza for 0.12 Include extremely terse summary of NEWS. --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9cf58ae..f2db1a7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +notmuch (0.12-1) unstable; urgency=low + + * New upstream release + - Python 3.2 support + - GMime 2.6 support + - Many updates to emacs interface (see /usr/share/doc/notmuch/NEWS) + - Ignore files/directories with mail hierarchy + + -- David Bremner Mon, 19 Mar 2012 22:41:58 -0300 + notmuch (0.12~rc2-1) experimental; urgency=low * Upstream pre-release -- cgit v1.2.3 From 0dcdc2ae8a352a164e052188cfb6224f24dad1ae Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 20 Mar 2012 07:47:24 -0300 Subject: add NEWS item for printing --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 5c54735..2e393c4 100644 --- a/NEWS +++ b/NEWS @@ -113,6 +113,11 @@ Enable `notmuch-search-line-faces' by default for new users by showing "unread" messages bold and "flagged" messages blue by default in the search view. +Printing Support + + notmuch-show mode now has simple printing support, bound to '#' by + default. You can customize the variable notmuch-print-mechanism. + Library changes --------------- -- cgit v1.2.3 From 524f01a7b5d146c70c5672f9ef9e0551745dd3f9 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 5 Mar 2012 11:23:43 +0100 Subject: Add GNU as a valid platform Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- configure | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure b/configure index ee0ae73..7b1d74e 100755 --- a/configure +++ b/configure @@ -365,9 +365,9 @@ elif [ $uname = "SunOS" ] ; then printf "Solaris.\n" platform=SOLARIS linker_resolves_library_dependencies=0 -elif [ $uname = "Linux" ] ; then - printf "Linux\n" - platform=LINUX +elif [ $uname = "Linux" ] || [ $uname = "GNU" ] ; then + printf "$uname\n" + platform="$uname" linker_resolves_library_dependencies=1 printf "Checking for $libdir_expanded in ldconfig... " -- cgit v1.2.3 From 1984a266469f4fda2de96ba93100ec38e82e3728 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 5 Mar 2012 11:23:44 +0100 Subject: Do not try to parse the options for --build and --host arguments Formerly the code assumed the arguments to be triples and threw an error if this was not the case. But those arguments are only there for compatibility with autotools and are not used within the build system, so just dropping the code parsing these values makes the build system more robust. Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de> --- configure | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/configure b/configure index 7b1d74e..3df2dc9 100755 --- a/configure +++ b/configure @@ -171,37 +171,9 @@ for option; do elif [ "${option}" = '--without-zsh-completion' ] ; then WITH_ZSH=0 elif [ "${option%%=*}" = '--build' ] ; then - build_option="${option#*=}" - case ${build_option} in - *-*-*) ;; - *) - echo "Unrecognized value for --build option: ${build_option}" - echo "Should be: --" - echo "See:" - echo " $0 --help" - echo "" - exit 1 - esac - build_cpu=${build_option%%-*} - build_option=${build_option#*-} - build_vendor=${build_option%%-*} - build_os=${build_option#*-} + true elif [ "${option%%=*}" = '--host' ] ; then - host_option="${option#*=}" - case ${host_option} in - *-*-*) ;; - *) - echo "Unrecognized value for --host option: ${host_option}" - echo "Should be: --" - echo "See:" - echo " $0 --help" - echo "" - exit 1 - esac - host_cpu=${host_option%%-*} - host_option=${host_option#*-} - host_vendor=${host_option%%-*} - host_os=${host_option#*-} + true elif [ "${option%%=*}" = '--infodir' ] ; then true elif [ "${option%%=*}" = '--datadir' ] ; then -- cgit v1.2.3 From 1ef9f769d78dcd7df80981ea435b246b378c6107 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 14 Mar 2012 16:59:59 +0200 Subject: configure: store $IFS to $DEFAULT_IFS readonly variable In the future, IFS value needs to be changed in a few places in configure -- and then restored. Store the original value to $DEFAULT_IFS for easy restoration. --- configure | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/configure b/configure index 3df2dc9..7056b4c 100755 --- a/configure +++ b/configure @@ -1,5 +1,8 @@ #! /bin/sh +# Store original IFS value so it can be changed (and restored) in many places. +readonly DEFAULT_IFS=$IFS + srcdir=$(dirname "$0") # For a non-srcdir configure invocation (such as ../configure), create @@ -351,7 +354,6 @@ elif [ $uname = "Linux" ] || [ $uname = "GNU" ] ; then # IFS=$(printf '\n') # # because the shell's command substitution deletes any trailing newlines. - OLD_IFS=$IFS IFS=" " for path in $ldconfig_paths; do @@ -359,7 +361,7 @@ elif [ $uname = "Linux" ] || [ $uname = "GNU" ] ; then libdir_in_ldconfig=1 fi done - IFS=$OLD_IFS + IFS=$DEFAULT_IFS if [ "$libdir_in_ldconfig" = '0' ]; then printf "No (will set RPATH)\n" else @@ -508,7 +510,7 @@ done printf "\n\t${WARN_CFLAGS}\n" rm -f minimal minimal.c - + cat < Date: Wed, 14 Mar 2012 17:00:00 +0200 Subject: Allow selecting which version of gmime is used to build notmuch. This allows for testing against both versions of gmime on a single machine, without having to mess with pkg-config paths. This is rework of Tom Prince's patch submitted in id:"1331402091-15663-1-git-send-email-tom.prince@ualberta.net" --- configure | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 7056b4c..8cf6f2d 100755 --- a/configure +++ b/configure @@ -48,6 +48,11 @@ WITH_EMACS=1 WITH_BASH=1 WITH_ZSH=1 +GMIME_24_VERSION='gmime-2.4' +GMIME_26_VERSION='gmime-2.6 >= 2.6.7' + +WITH_GMIME_VERSIONS="$GMIME_26_VERSION;$GMIME_24_VERSION" + usage () { cat <= 2.6.7' gmime-2.4; do +IFS=';' +for gmimepc in $WITH_GMIME_VERSIONS; do if pkg-config --exists $gmimepc; then printf "Yes ($gmimepc).\n" have_gmime=1 @@ -261,6 +273,7 @@ for gmimepc in 'gmime-2.6 >= 2.6.7' gmime-2.4; do break fi done +IFS=$DEFAULT_IFS if [ "$have_gmime" = "0" ]; then printf "No.\n" errors=$((errors + 1)) -- cgit v1.2.3 From 331f0cac61802606e0103c35453656d2299cbfe3 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Tue, 20 Mar 2012 15:43:35 -0300 Subject: debian: reword changelog about ignores --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index f2db1a7..d0eec37 100644 --- a/debian/changelog +++ b/debian/changelog @@ -4,9 +4,9 @@ notmuch (0.12-1) unstable; urgency=low - Python 3.2 support - GMime 2.6 support - Many updates to emacs interface (see /usr/share/doc/notmuch/NEWS) - - Ignore files/directories with mail hierarchy + - Optionally ignore some files/directories within mail hierarchy - -- David Bremner Mon, 19 Mar 2012 22:41:58 -0300 + -- David Bremner Tue, 20 Mar 2012 18:45:22 -0300 notmuch (0.12~rc2-1) experimental; urgency=low -- cgit v1.2.3 From 2b97293b15311f61dc73aa149484e7c1293f4ad2 Mon Sep 17 00:00:00 2001 From: Tomi Ollila Date: Wed, 21 Mar 2012 21:32:29 +0200 Subject: NEWS: consistent 2-space indentation in new content Some 0.12 NEWS items descriptions were indented with 3 spaces whereas all other lines are indented with 2 spaces. Brought those escaped lines back in line with others. --- NEWS | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index ed5e3c5..26c775c 100644 --- a/NEWS +++ b/NEWS @@ -67,18 +67,18 @@ Reply to sender Mail store folder/file ignore - A new configuration option, `new.ignore`, lets users specify a - ;-separated list of file and directory names that will not be - searched for messages by "notmuch new". + A new configuration option, `new.ignore`, lets users specify a + ;-separated list of file and directory names that will not be + searched for messages by "notmuch new". - NOTE: *Every* file/directory that goes by one of those names will - be ignored, independent of its depth/location in the mail store. + NOTE: *Every* file/directory that goes by one of those names will + be ignored, independent of its depth/location in the mail store. Unified help and manual pages - The notmuch help command now runs man for the appropriate page. If - you install notmuch somewhere "unusual", you may need to update - MANPATH. + The notmuch help command now runs man for the appropriate page. If + you install notmuch somewhere "unusual", you may need to update + MANPATH. Manual page for notmuch configuration options -- cgit v1.2.3 From d13810dc1e044446ad5eb85474c87e20924f9473 Mon Sep 17 00:00:00 2001 From: Stefano Zacchiroli Date: Mon, 26 Mar 2012 10:45:58 +0200 Subject: contrib: new mutt-notmuch utility for Mutt integration --- contrib/notmuch-mutt/.gitignore | 2 + contrib/notmuch-mutt/Makefile | 12 ++ contrib/notmuch-mutt/README | 59 +++++++++ contrib/notmuch-mutt/notmuch-mutt | 238 +++++++++++++++++++++++++++++++++++ contrib/notmuch-mutt/notmuch-mutt.rc | 9 ++ 5 files changed, 320 insertions(+) create mode 100644 contrib/notmuch-mutt/.gitignore create mode 100644 contrib/notmuch-mutt/Makefile create mode 100644 contrib/notmuch-mutt/README create mode 100755 contrib/notmuch-mutt/notmuch-mutt create mode 100644 contrib/notmuch-mutt/notmuch-mutt.rc diff --git a/contrib/notmuch-mutt/.gitignore b/contrib/notmuch-mutt/.gitignore new file mode 100644 index 0000000..682a577 --- /dev/null +++ b/contrib/notmuch-mutt/.gitignore @@ -0,0 +1,2 @@ +notmuch-mutt.1 +README.html diff --git a/contrib/notmuch-mutt/Makefile b/contrib/notmuch-mutt/Makefile new file mode 100644 index 0000000..87f9031 --- /dev/null +++ b/contrib/notmuch-mutt/Makefile @@ -0,0 +1,12 @@ +NAME = notmuch-mutt + +all: $(NAME) $(NAME).1 + +$(NAME).1: $(NAME) + pod2man $< > $@ + +README.html: README + markdown $< > $@ + +clean: + rm -f notmuch-mutt.1 README.html diff --git a/contrib/notmuch-mutt/README b/contrib/notmuch-mutt/README new file mode 100644 index 0000000..382ac91 --- /dev/null +++ b/contrib/notmuch-mutt/README @@ -0,0 +1,59 @@ +notmuch-mutt: Notmuch (of a) helper for Mutt +============================================ + +notmuch-mutt provide integration among the [Mutt] [1] mail user agent and the +[Notmuch] [2] mail indexer. + +notmuch-mutt offer two main integration features. The first one is the ability +of stating a **search query interactively** and then jump to a fresh Maildir +containing its search results only. The second one is the ability to +**reconstruct threads on the fly** starting from the currently highlighted +mail, which comes handy when a thread has been split across different maildirs, +archived, or the like. + +notmuch-mutt enables to trigger mail searches via a Mutt macro (usually F8) and +reconstruct threads via another (usually F9). Check the manpage for the 2-liner +configuration snippet for your Mutt configuration files (~/.muttrc, +/etc/Muttrc, or a /etc/Muttrc.d snippet). + +A [blog style introduction] [3] to notmuch-mutt is available and includes some +more rationale for its existence. + +Arguably, some of the logics of notmuch-mutt could disappear by adding support +for a --output=symlinks flag to notmuch. + + +[1]: http://www.mutt.org/ +[2]: http://notmuchmail.org/ +[3]: http://upsilon.cc/~zack/blog/posts/2011/01/how_to_use_Notmuch_with_Mutt/ + + +Requirements +------------ + +To *run* notmuch-mutt you will need Perl with the following libraries: + +- Mail::Box + (Debian package: libmail-box-perl) +- Mail::Internet + (Debian package: libmailtools-perl) +- String::ShellQuote + (Debian package: libstring-shellquote-perl) +- Term::ReadLine + (Debian package: libterm-readline-gnu-perl) + +To *build* notmuch-mutt documentation you will need: + +- pod2man (coming with Perl) to generate the manpage +- markdown to generate README.html out of this file + + +License +------- + +notmuch-mutt is copyright (C) 2011-2012 Stefano Zacchiroli . + +notmuch-mutt is released under the terms of the GNU General Public License +(GPL), version 3 or above. A copy of the license is available online at +. + diff --git a/contrib/notmuch-mutt/notmuch-mutt b/contrib/notmuch-mutt/notmuch-mutt new file mode 100755 index 0000000..9176ed5 --- /dev/null +++ b/contrib/notmuch-mutt/notmuch-mutt @@ -0,0 +1,238 @@ +#!/usr/bin/perl -w +# +# notmuch-mutt - notmuch (of a) helper for Mutt +# +# Copyright: 2011-2012 Stefano Zacchiroli +# License: GNU General Public License (GPL), version 3 or above +# +# See the bottom of this file for more documentation. +# A manpage can be obtained by running "pod2man notmuch-mutt > notmuch-mutt.1" + +use strict; +use warnings; + +use File::Path; +use Getopt::Long qw(:config no_getopt_compat); +use Mail::Internet; +use Mail::Box::Maildir; +use Pod::Usage; +use String::ShellQuote; +use Term::ReadLine; + + +my $xdg_cache_dir = "$ENV{HOME}/.cache"; +$xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME}; +my $cache_dir = "$xdg_cache_dir/notmuch/mutt"; + + +# create an empty maildir (if missing) or empty an existing maildir" +sub empty_maildir($) { + my ($maildir) = (@_); + rmtree($maildir) if (-d $maildir); + my $folder = new Mail::Box::Maildir(folder => $maildir, + create => 1); + $folder->close(); +} + +# search($maildir, $query) +# search mails according to $query with notmuch; store results in $maildir +sub search($$) { + my ($maildir, $query) = @_; + $query = shell_quote($query); + + empty_maildir($maildir); + system("notmuch search --output=files $query" + . " | sed -e 's: :\\\\ :g'" + . " | xargs --no-run-if-empty ln -s -t $maildir/cur/"); +} + +sub prompt($$) { + my ($text, $default) = @_; + my $query = ""; + my $term = Term::ReadLine->new( "notmuch-mutt" ); + my $histfile = "$cache_dir/history"; + + $term->ornaments( 0 ); + $term->unbind_key( ord( "\t" ) ); + $term->MinLine( 3 ); + $histfile = $ENV{MUTT_NOTMUCH_HISTFILE} if $ENV{MUTT_NOTMUCH_HISTFILE}; + $term->ReadHistory($histfile) if (-r $histfile); + while (1) { + chomp($query = $term->readline($text, $default)); + if ($query eq "?") { + system("man", "notmuch"); + } else { + $term->WriteHistory($histfile); + return $query; + } + } +} + +sub get_message_id() { + my $mail = Mail::Internet->new(\*STDIN); + $mail->head->get("message-id") =~ /^<(.*)>$/; # get message-id + return $1; +} + +sub search_action($$@) { + my ($interactive, $results_dir, @params) = @_; + + if (! $interactive) { + search($results_dir, join(' ', @params)); + } else { + my $query = prompt("search ('?' for man): ", join(' ', @params)); + if ($query ne "") { + search($results_dir,$query); + } + } +} + +sub thread_action(@) { + my ($results_dir, @params) = @_; + + my $mid = get_message_id(); + my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid"); + my $tid = `$search_cmd`; # get thread id + chomp($tid); + + search($results_dir, $tid); +} + +sub tag_action(@) { + my $mid = get_message_id(); + + system("notmuch tag " + . shell_quote(join(' ', @_)) + . " id:$mid"); +} + +sub die_usage() { + my %podflags = ( "verbose" => 1, + "exitval" => 2 ); + pod2usage(%podflags); +} + +sub main() { + mkpath($cache_dir) unless (-d $cache_dir); + + my $results_dir = "$cache_dir/results"; + my $interactive = 0; + my $help_needed = 0; + + my $getopt = GetOptions( + "h|help" => \$help_needed, + "o|output-dir=s" => \$results_dir, + "p|prompt" => \$interactive); + if (! $getopt || $#ARGV < 0) { die_usage() }; + my ($action, @params) = ($ARGV[0], @ARGV[1..$#ARGV]); + + foreach my $param (@params) { + $param =~ s/folder:=/folder:/g; + } + + if ($help_needed) { + die_usage(); + } elsif ($action eq "search" && $#ARGV == 0 && ! $interactive) { + print STDERR "Error: no search term provided\n\n"; + die_usage(); + } elsif ($action eq "search") { + search_action($interactive, $results_dir, @params); + } elsif ($action eq "thread") { + thread_action($results_dir, @params); + } elsif ($action eq "tag") { + tag_action(@params); + } else { + die_usage(); + } +} + +main(); + +__END__ + +=head1 NAME + +notmuch-mutt - notmuch (of a) helper for Mutt + +=head1 SYNOPSIS + +=over + +=item B [I