[PATCH] emacs: Make moving to the previous message move to the previous boundary

2012-07-18 Thread Mark Walters
On Sat, 14 Jul 2012, Austin Clements  wrote:
> Previously, notmuch-show-previous-message would move to the beginning
> of the message before the message containing point.  This patch makes
> it instead move to the previous message *boundary*.  That is, if point
> isn't already at the beginning of the message, it moves to the
> beginning of the current message.  This is consistent with
> notmuch-show-next-message, which can be thought of as moving to the
> next message boundary.  Several people have expressed a preference for
> this.

I like this change: the current behaviour has mildly confused/annoyed me
in the past.

>
> This patch accompanies the series in [0] (though they're independent
> and can be applied in either order).  This makes the behavior of 'p'
> and 'P' in show-mode conceptually similar to the new behavior of 'p'
> in search-mode.
>
> [0] 1342140319-19859-1-git-send-email-amdragon at mit.edu
>
>  emacs/notmuch-show.el |   10 +++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
> index 6335d45..02e319f 100644
> --- a/emacs/notmuch-show.el
> +++ b/emacs/notmuch-show.el
> @@ -1525,9 +1525,11 @@ thread, navigate to the next thread in the parent 
> search buffer."
>(goto-char (point-max)
>  
>  (defun notmuch-show-previous-message ()
> -  "Show the previous message."
> +  "Show the previous message or the start of the current message."
>(interactive)
> -  (notmuch-show-goto-message-previous)
> +  (if (= (point) (notmuch-show-message-top))
> +  (notmuch-show-goto-message-previous)
> +(notmuch-show-move-to-message-top))
>(notmuch-show-mark-read)
>(notmuch-show-message-adjust))
>  
> @@ -1587,7 +1589,9 @@ to show, nil otherwise."
>  (defun notmuch-show-previous-open-message ()
>"Show the previous open message."
>(interactive)
> -  (while (and (notmuch-show-goto-message-previous)
> +  (while (and (if (= (point) (notmuch-show-message-top))
> +   (notmuch-show-goto-message-previous)
> + (notmuch-show-move-to-message-top))
> (not (notmuch-show-message-visible-p
>(notmuch-show-mark-read)
>(notmuch-show-message-adjust))

I would mildly prefer taking the testing for top of message out of the
loop (I know it's one line longer) as

  (if (= (point) (notmuch-show-message-top))
  (notmuch-show-goto-message-previous)
(notmuch-show-move-to-message-top))
  (while (and (not (notmuch-show-message-visible-p))
  (notmuch-show-goto-message-previous)))

but its obviously fine as is.

Best wishes

Mark


[PATCH 1/7] go: Use iota in enum bindings

2012-07-18 Thread Sebastien Binet
Austin Clements  writes:

> Hah.  I guess nobody has tried to modify a notmuch database using the
> Go bindings.
>
> Could this instead assign the constants to
> C.NOTMUCH_DATABASE_MODE_READ_ONLY, etc, rather than duplicating their
> values?  It would be nice to do that for the Status values as well
> (which are correctly using iota, at least).

yep.
I have a few fixes like that in my repo:
https://github.com/sbinet/notmuch/commits/dev/go-bindings/bindings/go/src/notmuch

https://github.com/sbinet/notmuch/commit/38713de1b90a29b66b2a7f310065a3ffcf527d9b#L1L88

-s
-- next part --
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: not available
URL: 
<http://notmuchmail.org/pipermail/notmuch/attachments/20120718/e7f11955/attachment.pgp>


[PATCH 7/7] go: Bind notmuch_thread_t functions

2012-07-18 Thread Adrien Bustany
---
 bindings/go/src/notmuch/notmuch.go |  253 +++-
 1 files changed, 252 insertions(+), 1 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index be4cb8c..f667dbb 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -12,6 +12,8 @@ package notmuch
 */
 import "C"
 import "runtime"
+import "strings"
+import "time"
 import "unsafe"

 // Status codes used for the return values of most functions
@@ -700,7 +702,20 @@ func (self *Query) CountMessages() uint {
return uint(C.notmuch_query_count_messages(self.query))
 }

-// TODO: wrap threads and thread
+/* Return the number of threads matching a search.
+ *
+ * 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
+ * notmuch_query_count_messages().
+ *
+ * If an error occurs, this function may return 0.
+ */
+func (self *Query) CountThreads() uint {
+   return uint(C.notmuch_query_count_threads(self.query))
+}

 /* Is the given 'threads' iterator pointing at a valid thread.
  *
@@ -722,6 +737,45 @@ func (self *Threads) Valid() bool {
return true
 }

+/* Get the current thread from 'threads' as a notmuch_thread_t.
+ *
+ * Note: The returned thread belongs to 'threads' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+func (self *Threads) Get() *Thread {
+   if self.threads == nil {
+   return nil
+   }
+   thread := C.notmuch_threads_get(self.threads)
+   if thread == nil {
+   return nil
+   }
+   return createThread(thread, self)
+}
+
+/* Move the 'threads' iterator to the next thread.
+ *
+ * If 'threads' is already pointing at the last thread then the
+ * iterator will be moved to a point just beyond that last thread,
+ * (where notmuch_threads_valid will return FALSE and
+ * notmuch_threads_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+func (self *Threads) MoveToNext() {
+   if self.threads == nil {
+   return
+   }
+   C.notmuch_threads_move_to_next(self.threads)
+}
+
 /* Destroy a notmuch_threads_t object.
  *
  * It's not strictly necessary to call this function. All memory from
@@ -735,6 +789,203 @@ func (self *Threads) Destroy() {
}
 }

+/* Get the thread ID of 'thread'.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+func (self *Thread) GetThreadId() string {
+   if self.thread == nil {
+   return ""
+   }
+   id := C.notmuch_thread_get_thread_id(self.thread)
+
+   if id == nil {
+   return ""
+   }
+
+   return C.GoString(id)
+}
+
+/* Get the total number of messages in 'thread'.
+ *
+ * This count consists of all messages in the database belonging to
+ * this thread. Contrast with notmuch_thread_get_matched_messages() .
+ */
+func (self *Thread) GetTotalMessages() int {
+   if self.thread == nil {
+   return 0
+   }
+   return int(C.notmuch_thread_get_total_messages(self.thread))
+}
+
+/* Get a notmuch_messages_t iterator for the top-level messages in
+ * 'thread'.
+ *
+ * This iterator will not necessarily iterate over all of the messages
+ * in the thread. It will only iterate over the messages in the thread
+ * which are not replies to other messages in the thread.
+ *
+ * To iterate over all messages in the thread, the caller will need to
+ * iterate over the result of notmuch_message_get_replies for each
+ * top-level message (and do that recursively for the resulting
+ * messages, etc.).
+ */
+func (self *Thread) GetToplevelMessages() *Messages {
+   if self.thread == nil {
+   return nil
+   }
+   msgs := C.notmuch_thread_get_toplevel_messages(self.thread)
+   if msgs == nil {
+   return nil
+   }
+   return createMessages(msgs, self)
+}
+
+/* Get a notmuch_messages_t iterator for the top-level messages in
+ * 'thread'.
+ *
+ * This iterator will not necessarily iterate over all of the messages
+ * in the thread. It will only iterate over the messages in the thread
+ * which are not replies to other messages in the thread.
+ *
+ * To iterate over all messages in the thread, the caller will need to
+ * iterate over the result of 

[PATCH 6/7] go: Bind notmuch_database_find_message_by_filename

2012-07-18 Thread Adrien Bustany
---
 bindings/go/src/notmuch/notmuch.go |   39 
 1 files changed, 39 insertions(+), 0 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 384d5a5..be4cb8c 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -453,6 +453,45 @@ func (self *Database) FindMessage(message_id string) 
(*Message, Status) {
return createMessage(msg, nil), st
 }

+/* Find a message with the given filename.
+ *
+ * If the database contains a message with the given filename then, on
+ * successful return (NOTMUCH_STATUS_SUCCESS) '*message' will be initialized to
+ * a message object. The caller should call notmuch_message_destroy when done
+ * with the message.
+ *
+ * On any failure or when the message is not found, this function initializes
+ * '*message' to NULL. This means, when NOTMUCH_STATUS_SUCCESS is returned, the
+ * caller is supposed to check '*message' for NULL to find out whether the
+ * message with the given filename is found.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successful return, check '*message'
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'message' argument is NULL
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory, creating the message object
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
+ */
+func (self *Database) FindMessageByFilename(filename string) (*Message, 
Status) {
+
+   var c_msg_filename *C.char = C.CString(filename)
+   defer C.free(unsafe.Pointer(c_msg_filename))
+
+   if c_msg_filename == nil {
+   return nil, STATUS_OUT_OF_MEMORY
+   }
+
+   var msg *C.notmuch_message_t
+   st := Status(C.notmuch_database_find_message_by_filename(self.db, 
c_msg_filename, ))
+   if st != STATUS_SUCCESS {
+   return nil, st
+   }
+   return createMessage(msg, nil), st
+}
+
 /* Return a list of all tags found in the database.
  *
  * This function creates a list of all tags found in the database. The
-- 
1.7.7.6



[PATCH 5/7] go: Partially bind notmuch_database_upgrade

2012-07-18 Thread Adrien Bustany
This binding does not handle the progress callback, but at least allows
opening and upgrading a database if needed.
---
 bindings/go/src/notmuch/notmuch.go |   13 -
 1 files changed, 12 insertions(+), 1 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index d8b2739..384d5a5 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -296,7 +296,18 @@ func (self *Database) NeedsUpgrade() bool {
return true
 }

-// TODO: notmuch_database_upgrade
+// TODO: Proper notmuch_database_upgrade
+/* Upgrade the current database.
+ *
+ * After opening a database in read-write mode, the client should
+ * check if an upgrade is needed (notmuch_database_needs_upgrade) and
+ * if so, upgrade with this function before making any modifications.
+ */
+func (self *Database) Upgrade() Status {
+   st := Status(C.notmuch_database_upgrade(self.db, nil, nil));
+
+   return st;
+}

 /* Retrieve a directory object from the database for 'path'.
  *
-- 
1.7.7.6



[PATCH 4/7] go: Make Destroy functions safe to call several times

2012-07-18 Thread Adrien Bustany
Those methods were already checking if the underlying C object was NULL,
but they were not setting the pointer to NULL after destroying it.
---
 bindings/go/src/notmuch/notmuch.go |6 ++
 1 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 3f436a0..d8b2739 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -634,6 +634,7 @@ func (self *Query) SearchMessages() *Messages {
 func (self *Query) Destroy() {
if self.query != nil {
C.notmuch_query_destroy(self.query)
+   self.query = nil
}
 }

@@ -680,6 +681,7 @@ func (self *Threads) Valid() bool {
 func (self *Threads) Destroy() {
if self.threads != nil {
C.notmuch_threads_destroy(self.threads)
+   self.threads = nil
}
 }

@@ -1143,6 +1145,7 @@ func (self *Message) Destroy() {
return
}
C.notmuch_message_destroy(self.message)
+   self.message = nil
 }

 /* Is the given 'tags' iterator pointing at a valid tag.
@@ -1214,6 +1217,7 @@ func (self *Tags) Destroy() {
return
}
C.notmuch_tags_destroy(self.tags)
+   self.tags = nil
 }

 // TODO: wrap notmuch_directory_
@@ -1224,6 +1228,7 @@ func (self *Directory) Destroy() {
return
}
C.notmuch_directory_destroy(self.dir)
+   self.dir = nil
 }

 // TODO: wrap notmuch_filenames_
@@ -1242,6 +1247,7 @@ func (self *Filenames) Destroy() {
return
}
C.notmuch_filenames_destroy(self.fnames)
+   self.fnames = nil
 }

 /* EOF */
-- 
1.7.7.6



[PATCH 3/7] go: Allow notmuch objects to be garbage collected

2012-07-18 Thread Adrien Bustany
This makes notmuch appropriately free the underlying notmuch C objects
when garbage collecting their Go wrappers. To make sure we don't break
the underlying links between objects (for example, a notmuch_messages_t
being GC'ed before a notmuch_message_t belonging to it), we add for each
wraper struct a pointer to the owner object (Go objects with a reference
pointing to them don't get garbage collected).
---
 bindings/go/src/notmuch/notmuch.go |  153 +++-
 1 files changed, 134 insertions(+), 19 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 1d77fd2..3f436a0 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -11,6 +11,7 @@ package notmuch
 #include "notmuch.h"
 */
 import "C"
+import "runtime"
 import "unsafe"

 // Status codes used for the return values of most functions
@@ -47,40 +48,152 @@ func (self Status) String() string {
 /* Various opaque data types. For each notmuch__t see the various
  * notmuch_ functions below. */

+type Object interface {}
+
 type Database struct {
db *C.notmuch_database_t
 }

+func createDatabase(db *C.notmuch_database_t) *Database {
+   self := {db: db}
+
+   runtime.SetFinalizer(self, func(x *Database) {
+   if (x.db != nil) {
+   C.notmuch_database_destroy(x.db)
+   }
+   })
+
+   return self
+}
+
 type Query struct {
query *C.notmuch_query_t
+   owner Object
+}
+
+func createQuery(query *C.notmuch_query_t, owner Object) *Query {
+   self := {query: query, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Query) {
+   if (x.query != nil) {
+   C.notmuch_query_destroy(x.query)
+   }
+   })
+
+   return self
 }

 type Threads struct {
threads *C.notmuch_threads_t
+   owner Object
+}
+
+func createThreads(threads *C.notmuch_threads_t, owner Object) *Threads {
+   self := {threads: threads, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Threads) {
+   if (x.threads != nil) {
+   C.notmuch_threads_destroy(x.threads)
+   }
+   })
+
+   return self
 }

 type Thread struct {
thread *C.notmuch_thread_t
+   owner Object
+}
+
+func createThread(thread *C.notmuch_thread_t, owner Object) *Thread {
+   self := {thread: thread, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Thread) {
+   if (x.thread != nil) {
+   C.notmuch_thread_destroy(x.thread)
+   }
+   })
+
+   return self
 }

 type Messages struct {
messages *C.notmuch_messages_t
+   owner Object
+}
+
+func createMessages(messages *C.notmuch_messages_t, owner Object) *Messages {
+   self := {messages: messages, owner: owner}
+
+   return self
 }

 type Message struct {
message *C.notmuch_message_t
+   owner Object
+}
+
+func createMessage(message *C.notmuch_message_t, owner Object) *Message {
+   self := {message: message, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Message) {
+   if (x.message != nil) {
+   C.notmuch_message_destroy(x.message)
+   }
+   })
+
+   return self
 }

 type Tags struct {
tags *C.notmuch_tags_t
+   owner Object
+}
+
+func createTags(tags *C.notmuch_tags_t, owner Object) *Tags {
+   self := {tags: tags, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Tags) {
+   if (x.tags != nil) {
+   C.notmuch_tags_destroy(x.tags)
+   }
+   })
+
+   return self
 }

 type Directory struct {
dir *C.notmuch_directory_t
+   owner Object
+}
+
+func createDirectory(directory *C.notmuch_directory_t, owner Object) 
*Directory {
+   self := {dir: directory, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Directory) {
+   if (x.dir != nil) {
+   C.notmuch_directory_destroy(x.dir)
+   }
+   })
+
+   return self
 }

 type Filenames struct {
fnames *C.notmuch_filenames_t
+   owner Object
+}
+
+func createFilenames(filenames *C.notmuch_filenames_t, owner Object) 
*Filenames {
+   self := {fnames: filenames, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Filenames) {
+   if (x.fnames != nil) {
+   C.notmuch_filenames_destroy(x.fnames)
+   }
+   })
+
+   return self
 }

 type DatabaseMode C.notmuch_database_mode_t
@@ -100,12 +213,13 @@ func NewDatabase(path string) (*Database, Status) {
return nil, STATUS_OUT_OF_MEMORY
}

-   self := {db: nil}
-   st := Status(C.notmuch_database_create(c_path, ))
+   var db *C.notmuch_database_t;
+   st := Status(C.notmuch_database_create(c_path, ))
if st != STATUS_SUCCESS {

[PATCH 2/7] go: Add missing MESSAGE_FLAG_EXCLUDED in Flag enum

2012-07-18 Thread Adrien Bustany
---
 bindings/go/src/notmuch/notmuch.go |1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index ecd7418..1d77fd2 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -775,6 +775,7 @@ type Flag C.notmuch_message_flag_t

 const (
MESSAGE_FLAG_MATCH Flag = iota
+   MESSAGE_FLAG_EXCLUDED
 )

 /* Get a value of a flag for the email corresponding to 'message'. */
-- 
1.7.7.6



[PATCH 1/7] go: Use iota in enum bindings

2012-07-18 Thread Adrien Bustany
Using iota is the correct way to get the values in the enum increment
automatically. The old code would just set all the enum values to 0.
---
 bindings/go/src/notmuch/notmuch.go |6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 00bd53a..ecd7418 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -86,7 +86,7 @@ type Filenames struct {
 type DatabaseMode C.notmuch_database_mode_t

 const (
-   DATABASE_MODE_READ_ONLY DatabaseMode = 0
+   DATABASE_MODE_READ_ONLY DatabaseMode = iota
DATABASE_MODE_READ_WRITE
 )

@@ -386,7 +386,7 @@ func (self *Database) CreateQuery(query string) *Query {
 type Sort C.notmuch_sort_t

 const (
-   SORT_OLDEST_FIRST Sort = 0
+   SORT_OLDEST_FIRST Sort = iota
SORT_NEWEST_FIRST
SORT_MESSAGE_ID
SORT_UNSORTED
@@ -774,7 +774,7 @@ func (self *Message) GetFileName() string {
 type Flag C.notmuch_message_flag_t

 const (
-   MESSAGE_FLAG_MATCH Flag = 0
+   MESSAGE_FLAG_MATCH Flag = iota
 )

 /* Get a value of a flag for the email corresponding to 'message'. */
-- 
1.7.7.6



[PATCH 0/7] Various fixes for the Go bindings

2012-07-18 Thread Adrien Bustany
The following patches fix some serious memory management issues with
the Go bindings, and add some missing functions as well.

Adrien Bustany (7):
  go: Use iota in enum bindings
  go: Add missing MESSAGE_FLAG_EXCLUDED in Flag enum
  go: Allow notmuch objects to be garbage collected
  go: Make Destroy functions safe to call several times
  go: Partially bind notmuch_database_upgrade
  go: Bind notmuch_database_find_message_by_filename
  go: Bind notmuch_thread_t functions

 bindings/go/src/notmuch/notmuch.go |  471 ++--
 1 files changed, 447 insertions(+), 24 deletions(-)

-- 
1.7.7.6



[PATCHv2] cli: Hooks for tag-command

2012-07-18 Thread Dominik Peteler
hello,

I improved my patch according to Janis mail:
 * new cli syntax: notmuch tag [ --no-hooks ] --  [ -- ] 
 * adjusted man pages and wrote tests

I had the idea to improve this feature by passing the message-ids or the 
filename to the hooks.
What's your opinion about that ? Any suggestions ?

regards

dominik



There are two hooks:
 * pre-tag: Run before tagging
 * post-tag: Run after

This allows users to react on changes of tags. For example,
you might want to move a message to a special Maildir
depending on its notmuch tags.
---
 man/man1/notmuch-tag.1   | 22 +++-
 man/man5/notmuch-hooks.5 | 19 ++
 notmuch-tag.c| 52 +---
 test/hooks   | 36 +
 test/tagging | 28 ++
 5 files changed, 153 insertions(+), 4 deletions(-)

diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1
index d810e1b..e00e189 100644
--- a/man/man1/notmuch-tag.1
+++ b/man/man1/notmuch-tag.1
@@ -4,7 +4,11 @@ notmuch-tag \- add/remove tags for all messages matching the 
search terms

 .SH SYNOPSIS
 .B notmuch tag
-.RI  "+<" tag> "|\-<" tag "> [...] [\-\-] <" search-term ">..."
+.RI  "+<" tag "> |\-<" tag "> [...] [\-\-] <" search-term ">..."
+
+.B notmuch tag
+.RB "[" --no-hooks "]"
+.RI "\-\- +<" tag "> |\-<" tag "> [...] \-\- <" search-term ">..."

 .SH DESCRIPTION

@@ -29,6 +33,22 @@ updates the maildir flags according to tag changes if the
 configuration option is enabled. See \fBnotmuch-config\fR(1) for
 details.

+The
+.B tag
+command supports hooks. See  \fBnotmuch-hooks(5)\fR
+for more details on hooks.
+
+Supported options for
+.B tag
+include
+.RS 4
+.TP 4
+.BR \-\-no\-hooks
+
+Prevents hooks from being run.
+.RE
+.RE
+
 .SH SEE ALSO

 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5
index b914a29..e193ef5 100644
--- a/man/man5/notmuch-hooks.5
+++ b/man/man5/notmuch-hooks.5
@@ -38,6 +38,25 @@ the scan or import.
 Typically this hook is used to perform additional query\-based tagging on the
 imported messages.
 .RE
+.RS 4
+.TP 4
+.B pre\-tag
+This hook is invoked by the
+.B tag
+command before tagging messages. If this
+hook exits with a non-zero status, notmuch will abort further processing of the
+.B tag
+command.
+.RE
+.RS 4
+.TP 4
+.B post\-tag
+This hook is invoked by the
+.B tag
+command after messages have been tagged. The hook will not be run if there 
have been any errors during
+the tagging.
+.RE
+

 .SH SEE ALSO

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 7d18639..7572059 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -174,9 +174,17 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 int tag_ops_count = 0;
 char *query_string;
 notmuch_config_t *config;
+const char *db_path;
 notmuch_database_t *notmuch;
 struct sigaction action;
 notmuch_bool_t synchronize_flags;
+/* Points to the position of the "--" delimiters, e. g.
+ * arg_delimiters[0]  arg_delimiters[1] 

+ *
+ * arg_delimiters[0] may remain -1 if there are no arguments given
+ * arg_delimiters[0] may remain -1 if there is no delimiter between tag 
ops and search terms */
+int arg_delimiters[2] = {-1, -1};
+notmuch_bool_t run_hooks = TRUE;
 int i;
 int ret;

@@ -197,11 +205,37 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
return 1;
 }

+/* Determine position of delimiters */
 for (i = 0; i < argc; i++) {
if (strcmp (argv[i], "--") == 0) {
-   i++;
-   break;
+   if (arg_delimiters[1] == -1) {
+   arg_delimiters[1] = i;
+   } else if (arg_delimiters[0] == -1) {
+   arg_delimiters[0] = arg_delimiters[1];
+   arg_delimiters[1] = i;
+   } else {
+   fprintf (stderr, "Error: 'notmuch tag' requires delimiter 
\"--\" at most two times.\n");
+   return 1;
+   }
}
+}
+
+/* Process arguments if present */
+for (i = 0; i < arg_delimiters[0]; i++) {
+   if (strcmp (argv[i], "--no-hooks") == 0) {
+   run_hooks = FALSE;
+   } else {
+   fprintf (stderr, "Error: 'notmuch tag' doesn't recognize argument 
'%s'.\n", argv[i]);
+   return 1;
+   }
+}
+
+/* Set arg_delimiters[1] to argc if no delimiters at all are present */
+if (arg_delimiters[1] == -1)
+   arg_delimiters[1] = argc;
+
+/* Read tag ops */
+for (i = arg_delimiters[0]+1; i < arg_delimiters[1]; i++) {
if (argv[i][0] == '+' || argv[i][0] == '-') {
tag_ops[tag_ops_count].tag = argv[i] + 1;
tag_ops[tag_ops_count].remove = (argv[i][0] == '-');
@@ -229,7 +263,15 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 if (config == NULL)
return 1;

-if (notmuch_database_open 

[PATCH 0/7] Various fixes for the Go bindings

2012-07-18 Thread Austin Clements
This series looks good to me other than the few things I commented on.
It's nice to see the Go bindings get a bit of love!

Quoth Adrien Bustany on Jul 18 at  9:34 pm:
> The following patches fix some serious memory management issues with
> the Go bindings, and add some missing functions as well.
> 
> Adrien Bustany (7):
>   go: Use iota in enum bindings
>   go: Add missing MESSAGE_FLAG_EXCLUDED in Flag enum
>   go: Allow notmuch objects to be garbage collected
>   go: Make Destroy functions safe to call several times
>   go: Partially bind notmuch_database_upgrade
>   go: Bind notmuch_database_find_message_by_filename
>   go: Bind notmuch_thread_t functions
> 
>  bindings/go/src/notmuch/notmuch.go |  471 
> ++--
>  1 files changed, 447 insertions(+), 24 deletions(-)
> 


[PATCH 7/7] go: Bind notmuch_thread_t functions

2012-07-18 Thread Austin Clements
Quoth Adrien Bustany on Jul 18 at  9:34 pm:
> ---
>  bindings/go/src/notmuch/notmuch.go |  253 
> +++-
>  1 files changed, 252 insertions(+), 1 deletions(-)
> 
> diff --git a/bindings/go/src/notmuch/notmuch.go 
> b/bindings/go/src/notmuch/notmuch.go
> index be4cb8c..f667dbb 100644
> --- a/bindings/go/src/notmuch/notmuch.go
> +++ b/bindings/go/src/notmuch/notmuch.go
> @@ -12,6 +12,8 @@ package notmuch
>  */
>  import "C"
>  import "runtime"
> +import "strings"
> +import "time"
>  import "unsafe"
>  
>  // Status codes used for the return values of most functions
> @@ -700,7 +702,20 @@ func (self *Query) CountMessages() uint {
>   return uint(C.notmuch_query_count_messages(self.query))
>  }
>  
> -// TODO: wrap threads and thread
> +/* Return the number of threads matching a search.
> + *
> + * 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
> + * notmuch_query_count_messages().
> + *
> + * If an error occurs, this function may return 0.
> + */
> +func (self *Query) CountThreads() uint {
> + return uint(C.notmuch_query_count_threads(self.query))
> +}
>  
>  /* Is the given 'threads' iterator pointing at a valid thread.
>   *
> @@ -722,6 +737,45 @@ func (self *Threads) Valid() bool {
>   return true
>  }
>  
> +/* Get the current thread from 'threads' as a notmuch_thread_t.
> + *
> + * Note: The returned thread belongs to 'threads' and has a lifetime
> + * identical to it (and the query to which it belongs).
> + *
> + * See the documentation of notmuch_query_search_threads for example
> + * code showing how to iterate over a notmuch_threads_t object.
> + *
> + * If an out-of-memory situation occurs, this function will return
> + * NULL.
> + */
> +func (self *Threads) Get() *Thread {
> + if self.threads == nil {
> + return nil
> + }
> + thread := C.notmuch_threads_get(self.threads)
> + if thread == nil {
> + return nil
> + }
> + return createThread(thread, self)
> +}
> +
> +/* Move the 'threads' iterator to the next thread.
> + *
> + * If 'threads' is already pointing at the last thread then the
> + * iterator will be moved to a point just beyond that last thread,
> + * (where notmuch_threads_valid will return FALSE and
> + * notmuch_threads_get will return NULL).
> + *
> + * See the documentation of notmuch_query_search_threads for example
> + * code showing how to iterate over a notmuch_threads_t object.
> + */
> +func (self *Threads) MoveToNext() {
> + if self.threads == nil {
> + return
> + }
> + C.notmuch_threads_move_to_next(self.threads)
> +}
> +
>  /* Destroy a notmuch_threads_t object.
>   *
>   * It's not strictly necessary to call this function. All memory from
> @@ -735,6 +789,203 @@ func (self *Threads) Destroy() {
>   }
>  }
>  
> +/* Get the thread ID of 'thread'.
> + *
> + * The returned string belongs to 'thread' and as such, should not be
> + * modified by the caller and will only be valid for as long as the
> + * thread is valid, (which is until notmuch_thread_destroy or until
> + * the query from which it derived is destroyed).
> + */
> +func (self *Thread) GetThreadId() string {
> + if self.thread == nil {
> + return ""
> + }
> + id := C.notmuch_thread_get_thread_id(self.thread)
> +
> + if id == nil {
> + return ""
> + }
> +
> + return C.GoString(id)
> +}
> +
> +/* Get the total number of messages in 'thread'.
> + *
> + * This count consists of all messages in the database belonging to
> + * this thread. Contrast with notmuch_thread_get_matched_messages() .
> + */
> +func (self *Thread) GetTotalMessages() int {
> + if self.thread == nil {
> + return 0
> + }
> + return int(C.notmuch_thread_get_total_messages(self.thread))
> +}
> +
> +/* Get a notmuch_messages_t iterator for the top-level messages in
> + * 'thread'.
> + *
> + * This iterator will not necessarily iterate over all of the messages
> + * in the thread. It will only iterate over the messages in the thread
> + * which are not replies to other messages in the thread.
> + *
> + * To iterate over all messages in the thread, the caller will need to
> + * iterate over the result of notmuch_message_get_replies for each
> + * top-level message (and do that recursively for the resulting
> + * messages, etc.).
> + */
> +func (self *Thread) GetToplevelMessages() *Messages {
> + if self.thread == nil {
> + return nil
> + }
> + msgs := C.notmuch_thread_get_toplevel_messages(self.thread)
> + if msgs == nil {
> + return nil
> + }
> + return createMessages(msgs, self)
> +}
> +
> +/* Get a notmuch_messages_t iterator for the top-level messages in
> + * 'thread'.
> + *
> + * This iterator will not necessarily iterate 

[PATCH 5/7] go: Partially bind notmuch_database_upgrade

2012-07-18 Thread Austin Clements
Quoth Adrien Bustany on Jul 18 at  9:34 pm:
> This binding does not handle the progress callback, but at least allows
> opening and upgrading a database if needed.
> ---
>  bindings/go/src/notmuch/notmuch.go |   13 -
>  1 files changed, 12 insertions(+), 1 deletions(-)
> 
> diff --git a/bindings/go/src/notmuch/notmuch.go 
> b/bindings/go/src/notmuch/notmuch.go
> index d8b2739..384d5a5 100644
> --- a/bindings/go/src/notmuch/notmuch.go
> +++ b/bindings/go/src/notmuch/notmuch.go
> @@ -296,7 +296,18 @@ func (self *Database) NeedsUpgrade() bool {
>   return true
>  }
>  
> -// TODO: notmuch_database_upgrade
> +// TODO: Proper notmuch_database_upgrade
> +/* Upgrade the current database.
> + *
> + * After opening a database in read-write mode, the client should
> + * check if an upgrade is needed (notmuch_database_needs_upgrade) and
> + * if so, upgrade with this function before making any modifications.
> + */
> +func (self *Database) Upgrade() Status {
> + st := Status(C.notmuch_database_upgrade(self.db, nil, nil));
> +
> + return st;

*gasp*  Semicolons!

> +}
>  
>  /* Retrieve a directory object from the database for 'path'.
>   *


[PATCH 3/7] go: Allow notmuch objects to be garbage collected

2012-07-18 Thread Austin Clements
This is subtle enough that I think it deserves a comment in the source
code explaining that tracking the talloc owner reference, combined
with the fact that Go finalizers are run in dependency order, ensures
that the C objects will always be destroyed from the talloc leaves up.

Just one inline comment below.  Otherwise, I think this is all
correct.

Is reproducing the talloc hierarchy in all of the bindings really the
right approach?  I feel like there has to be a better way (or that the
way we use talloc in the library is slightly broken).  What if the
bindings created an additional talloc reference to each managed
object, just to keep the object alive, and used talloc_unlink instead
of the destroy functions?

Quoth Adrien Bustany on Jul 18 at  9:34 pm:
> This makes notmuch appropriately free the underlying notmuch C objects
> when garbage collecting their Go wrappers. To make sure we don't break
> the underlying links between objects (for example, a notmuch_messages_t
> being GC'ed before a notmuch_message_t belonging to it), we add for each
> wraper struct a pointer to the owner object (Go objects with a reference
> pointing to them don't get garbage collected).
> ---
>  bindings/go/src/notmuch/notmuch.go |  153 
> +++-
>  1 files changed, 134 insertions(+), 19 deletions(-)
> 
> diff --git a/bindings/go/src/notmuch/notmuch.go 
> b/bindings/go/src/notmuch/notmuch.go
> index 1d77fd2..3f436a0 100644
> --- a/bindings/go/src/notmuch/notmuch.go
> +++ b/bindings/go/src/notmuch/notmuch.go
> @@ -11,6 +11,7 @@ package notmuch
>  #include "notmuch.h"
>  */
>  import "C"
> +import "runtime"
>  import "unsafe"
>  
>  // Status codes used for the return values of most functions
> @@ -47,40 +48,152 @@ func (self Status) String() string {
>  /* Various opaque data types. For each notmuch__t see the various
>   * notmuch_ functions below. */
>  
> +type Object interface {}
> +
>  type Database struct {
>   db *C.notmuch_database_t
>  }
>  
> +func createDatabase(db *C.notmuch_database_t) *Database {
> + self := {db: db}
> +
> + runtime.SetFinalizer(self, func(x *Database) {
> + if (x.db != nil) {
> + C.notmuch_database_destroy(x.db)
> + }
> + })
> +
> + return self
> +}
> +
>  type Query struct {
>   query *C.notmuch_query_t
> + owner Object
> +}
> +
> +func createQuery(query *C.notmuch_query_t, owner Object) *Query {
> + self := {query: query, owner: owner}
> +
> + runtime.SetFinalizer(self, func(x *Query) {
> + if (x.query != nil) {
> + C.notmuch_query_destroy(x.query)
> + }
> + })
> +
> + return self
>  }
>  
>  type Threads struct {
>   threads *C.notmuch_threads_t
> + owner Object
> +}
> +
> +func createThreads(threads *C.notmuch_threads_t, owner Object) *Threads {
> + self := {threads: threads, owner: owner}
> +
> + runtime.SetFinalizer(self, func(x *Threads) {
> + if (x.threads != nil) {
> + C.notmuch_threads_destroy(x.threads)
> + }
> + })
> +
> + return self
>  }
>  
>  type Thread struct {
>   thread *C.notmuch_thread_t
> + owner Object
> +}
> +
> +func createThread(thread *C.notmuch_thread_t, owner Object) *Thread {
> + self := {thread: thread, owner: owner}
> +
> + runtime.SetFinalizer(self, func(x *Thread) {
> + if (x.thread != nil) {
> + C.notmuch_thread_destroy(x.thread)
> + }
> + })
> +
> + return self
>  }
>  
>  type Messages struct {
>   messages *C.notmuch_messages_t
> + owner Object
> +}
> +
> +func createMessages(messages *C.notmuch_messages_t, owner Object) *Messages {
> + self := {messages: messages, owner: owner}
> +
> + return self
>  }
>  
>  type Message struct {
>   message *C.notmuch_message_t
> + owner Object
> +}
> +
> +func createMessage(message *C.notmuch_message_t, owner Object) *Message {
> + self := {message: message, owner: owner}
> +
> + runtime.SetFinalizer(self, func(x *Message) {
> + if (x.message != nil) {
> + C.notmuch_message_destroy(x.message)
> + }
> + })
> +
> + return self
>  }
>  
>  type Tags struct {
>   tags *C.notmuch_tags_t
> + owner Object
> +}
> +
> +func createTags(tags *C.notmuch_tags_t, owner Object) *Tags {
> + self := {tags: tags, owner: owner}
> +
> + runtime.SetFinalizer(self, func(x *Tags) {
> + if (x.tags != nil) {
> + C.notmuch_tags_destroy(x.tags)
> + }
> + })
> +
> + return self
>  }
>  
>  type Directory struct {
>   dir *C.notmuch_directory_t
> + owner Object
> +}
> +
> +func createDirectory(directory *C.notmuch_directory_t, owner Object) 
> *Directory {
> + self := {dir: directory, owner: owner}
> +
> + runtime.SetFinalizer(self, func(x *Directory) {
> + if (x.dir != nil) {
> 

[PATCH 1/7] go: Use iota in enum bindings

2012-07-18 Thread Austin Clements
Hah.  I guess nobody has tried to modify a notmuch database using the
Go bindings.

Could this instead assign the constants to
C.NOTMUCH_DATABASE_MODE_READ_ONLY, etc, rather than duplicating their
values?  It would be nice to do that for the Status values as well
(which are correctly using iota, at least).

Quoth Adrien Bustany on Jul 18 at  9:34 pm:
> Using iota is the correct way to get the values in the enum increment
> automatically. The old code would just set all the enum values to 0.
> ---
>  bindings/go/src/notmuch/notmuch.go |6 +++---
>  1 files changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/bindings/go/src/notmuch/notmuch.go 
> b/bindings/go/src/notmuch/notmuch.go
> index 00bd53a..ecd7418 100644
> --- a/bindings/go/src/notmuch/notmuch.go
> +++ b/bindings/go/src/notmuch/notmuch.go
> @@ -86,7 +86,7 @@ type Filenames struct {
>  type DatabaseMode C.notmuch_database_mode_t
>  
>  const (
> - DATABASE_MODE_READ_ONLY DatabaseMode = 0
> + DATABASE_MODE_READ_ONLY DatabaseMode = iota
>   DATABASE_MODE_READ_WRITE
>  )
>  
> @@ -386,7 +386,7 @@ func (self *Database) CreateQuery(query string) *Query {
>  type Sort C.notmuch_sort_t
>  
>  const (
> - SORT_OLDEST_FIRST Sort = 0
> + SORT_OLDEST_FIRST Sort = iota
>   SORT_NEWEST_FIRST
>   SORT_MESSAGE_ID
>   SORT_UNSORTED
> @@ -774,7 +774,7 @@ func (self *Message) GetFileName() string {
>  type Flag C.notmuch_message_flag_t
>  
>  const (
> - MESSAGE_FLAG_MATCH Flag = 0
> + MESSAGE_FLAG_MATCH Flag = iota
>  )
>  
>  /* Get a value of a flag for the email corresponding to 'message'. */


[PATCH v6 3/3] Use the structured formatters in notmuch-search.c.

2012-07-18 Thread Austin Clements
Just a few comments (don't forget to scroll all the way down).
Overall this is looking pretty good.

Quoth craven at gmx.net on Jul 16 at 10:35 am:
> This patch switches from the current ad-hoc printer to the structured
> formatters in sprinter.h, sprinter-text-search.c and sprinter-json.c.
> 
> The JSON tests are changed slightly in order to make them PASS for the
> new structured output formatter.
> 
> The text tests pass without adaptation.
> ---
>  notmuch-search.c | 300 
> ---
>  test/json|  18 +---
>  2 files changed, 86 insertions(+), 232 deletions(-)

That's a fantastic diffstat.

> 
> diff --git a/notmuch-search.c b/notmuch-search.c
> index 3be296d..cf927e6 100644
> --- a/notmuch-search.c
> +++ b/notmuch-search.c
> @@ -19,6 +19,7 @@
>   */
>  
>  #include "notmuch-client.h"
> +#include "sprinter.h"
>  
>  typedef enum {
>  OUTPUT_SUMMARY,
> @@ -28,92 +29,6 @@ typedef enum {
>  OUTPUT_TAGS
>  } output_t;
>  
> -typedef struct search_format {
> -const char *results_start;
> -const char *item_start;
> -void (*item_id) (const void *ctx,
> -  const char *item_type,
> -  const char *item_id);
> -void (*thread_summary) (const void *ctx,
> - const char *thread_id,
> - const time_t date,
> - const int matched,
> - const int total,
> - const char *authors,
> - const char *subject);
> -const char *tag_start;
> -const char *tag;
> -const char *tag_sep;
> -const char *tag_end;
> -const char *item_sep;
> -const char *item_end;
> -const char *results_end;
> -const char *results_null;
> -} search_format_t;
> -
> -static void
> -format_item_id_text (const void *ctx,
> -  const char *item_type,
> -  const char *item_id);
> -
> -static void
> -format_thread_text (const void *ctx,
> - const char *thread_id,
> - const time_t date,
> - const int matched,
> - const int total,
> - const char *authors,
> - const char *subject);
> -static const search_format_t format_text = {
> -"",
> - "",
> - format_item_id_text,
> - format_thread_text,
> - " (",
> - "%s", " ",
> - ")", "\n",
> - "",
> -"\n",
> -"",
> -};
> -
> -static void
> -format_item_id_json (const void *ctx,
> -  const char *item_type,
> -  const char *item_id);
> -
> -static void
> -format_thread_json (const void *ctx,
> - const char *thread_id,
> - const time_t date,
> - const int matched,
> - 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 = {
> -"[",
> - "{",
> - format_item_id_json,
> - format_thread_json,
> - "\"tags\": [",
> - "\"%s\"", ", ",
> - "]", ",\n",
> - "}",
> -"]\n",
> -"]\n",
> -};
> -
> -static void
> -format_item_id_text (unused (const void *ctx),
> -  const char *item_type,
> -  const char *item_id)
> -{
> -printf ("%s%s", item_type, item_id);
> -}
> -
>  static char *
>  sanitize_string (const void *ctx, const char *str)
>  {
> @@ -131,72 +46,8 @@ sanitize_string (const void *ctx, const char *str)
>  return out;
>  }
>  
> -static void
> -format_thread_text (const void *ctx,
> - const char *thread_id,
> - const time_t date,
> - const int matched,
> - const int total,
> - const char *authors,
> - const char *subject)
> -{
> -void *ctx_quote = talloc_new (ctx);
> -
> -printf ("thread:%s %12s [%d/%d] %s; %s",
> - thread_id,
> - notmuch_time_relative_date (ctx, date),
> - matched,
> - total,
> - sanitize_string (ctx_quote, authors),
> - sanitize_string (ctx_quote, subject));
> -
> -talloc_free (ctx_quote);
> -}
> -
> -static void
> -format_item_id_json (const void *ctx,
> -  unused (const char *item_type),
> -  const char *item_id)
> -{
> -void *ctx_quote = talloc_new (ctx);
> -
> -printf ("%s", json_quote_str (ctx_quote, item_id));
> -
> -talloc_free (ctx_quote);
> -
> -}
> -
> -static void
> -format_thread_json (const void *ctx,
> - const char *thread_id,
> - const time_t date,
> - const int matched,
> - const int total,
> - const char *authors,
> - const char *subject)
> -{
> -void 

[PATCH v6 2/3] Add structured output formatter for JSON and plain text.

2012-07-18 Thread Austin Clements
Quoth craven at gmx.net on Jul 16 at 10:35 am:
> Using the new structured printer support in sprinter.h, implement
> sprinter_json_create, which returns a new JSON structured output
> formatter. The formatter prints output similar to the existing JSON, but
> with differences in whitespace (mostly newlines, --output=summary prints
> the entire message summary on one line, not split across multiple lines).
> 
> Also implement a "structured" formatter for plain text that prints
> prefixed strings, to be used with notmuch-search.c plain text output.
> ---
>  Makefile.local |   2 +
>  sprinter-json.c| 191 
> +
>  sprinter-text-search.c | 146 +
>  sprinter.h |   9 +++
>  4 files changed, 348 insertions(+)
>  create mode 100644 sprinter-json.c
>  create mode 100644 sprinter-text-search.c
> 
> diff --git a/Makefile.local b/Makefile.local
> index a890df2..4f534f1 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -290,6 +290,8 @@ notmuch_client_srcs = \
>   notmuch-show.c  \
>   notmuch-tag.c   \
>   notmuch-time.c  \
> + sprinter-text-search.c  \
> + sprinter-json.c \
>   query-string.c  \
>   mime-node.c \
>   crypto.c\
> diff --git a/sprinter-json.c b/sprinter-json.c
> new file mode 100644
> index 000..a93a390
> --- /dev/null
> +++ b/sprinter-json.c
> @@ -0,0 +1,191 @@
> +#include 
> +#include 
> +#include 
> +#include "sprinter.h"
> +
> +struct sprinter_json {
> +struct sprinter vtable;
> +FILE *stream;
> +/* Top of the state stack, or NULL if the printer is not currently
> + * inside any aggregate types. */
> +struct json_state *state;
> +
> +/* A flag to signify that a separator should be inserted in the
> + * output as soon as possible.
> + */
> +notmuch_bool_t insert_separator;
> +};
> +
> +struct json_state {
> +struct json_state *parent;
> +/* True if nothing has been printed in this aggregate yet.
> + * Suppresses the comma before a value. */
> +notmuch_bool_t first;
> +/* The character that closes the current aggregate. */
> +char close;
> +};
> +
> +/* Helper function to set up the stream to print a value.  If this
> + * value follows another value, prints a comma. */
> +static struct sprinter_json *
> +json_begin_value (struct sprinter *sp)
> +{
> +struct sprinter_json *spj = (struct sprinter_json *) sp;
> +
> +if (spj->state) {
> + if (! spj->state->first) {
> + fputc (',', spj->stream);
> + if (spj->insert_separator) {
> + fputc ('\n', spj->stream);
> + spj->insert_separator = FALSE;
> + } else
> + fputc (' ', spj->stream);
> + } else
> + spj->state->first = FALSE;
> +}
> +return spj;
> +}
> +
> +/* Helper function to begin an aggregate type.  Prints the open
> + * character and pushes a new state frame. */
> +static void
> +json_begin_aggregate (struct sprinter *sp, char open, char close)
> +{
> +struct sprinter_json *spj = json_begin_value (sp);
> +struct json_state *state = talloc (spj, struct json_state);
> +
> +fputc (open, spj->stream);
> +state->parent = spj->state;
> +state->first = TRUE;
> +state->close = close;
> +spj->state = state;
> +}
> +
> +static void
> +json_begin_map (struct sprinter *sp)
> +{
> +json_begin_aggregate (sp, '{', '}');
> +}
> +
> +static void
> +json_begin_list (struct sprinter *sp)
> +{
> +json_begin_aggregate (sp, '[', ']');
> +}
> +
> +static void
> +json_end (struct sprinter *sp)
> +{
> +struct sprinter_json *spj = (struct sprinter_json *) sp;
> +struct json_state *state = spj->state;
> +
> +fputc (spj->state->close, spj->stream);
> +spj->state = state->parent;
> +talloc_free (state);
> +if (spj->state == NULL)
> + fputc ('\n', spj->stream);
> +}
> +
> +static void
> +json_string (struct sprinter *sp, const char *val)
> +{
> +static const char *const escapes[] = {
> + ['\"'] = "\\\"", ['\\'] = "", ['\b'] = "\\b",
> + ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
> +};
> +struct sprinter_json *spj = json_begin_value (sp);
> +
> +fputc ('"', spj->stream);
> +for (; *val; ++val) {
> + unsigned char ch = *val;
> + if (ch < ARRAY_SIZE (escapes) && escapes[ch])
> + fputs (escapes[ch], spj->stream);
> + else if (ch >= 32)
> + fputc (ch, spj->stream);
> + else
> + fprintf (spj->stream, "\\u%04x", ch);
> +}
> +fputc ('"', spj->stream);
> +}
> +
> +static void
> +json_integer (struct sprinter *sp, int val)
> +{
> +struct sprinter_json *spj = json_begin_value (sp);
> +
> +fprintf (spj->stream, "%d", val);
> +}
> +
> +static void
> +json_boolean (struct sprinter *sp, notmuch_bool_t val)
> +{
> +struct sprinter_json *spj = json_begin_value 

[PATCH] cli: Hooks for tag-command

2012-07-18 Thread Jani Nikula
otmuch_config_get_database_path (config),
> +db_path = notmuch_config_get_database_path (config);
> +
> +if (run_hooks) {
> +   ret = notmuch_run_hook (db_path, "pre-tag");
> +   if (ret)
> +   return ret;
> +}
> +
> +if (notmuch_database_open (db_path,
>NOTMUCH_DATABASE_MODE_READ_WRITE,
))
> return 1;
>
> @@ -239,5 +250,11 @@ notmuch_tag_command (void *ctx, int argc, char
*argv[])
>
>  notmuch_database_destroy (notmuch);
>
> +if (run_hooks) {

Can't check further context atm, are you sure not to run post-tag if
there's an error?

> +   ret = notmuch_run_hook (db_path, "post-tag");
> +   if (ret)
> +   return ret;
> +}
> +
>  return ret;
>  }
> diff --git a/test/hooks b/test/hooks
> index 77e8569..ae857cc 100755
> --- a/test/hooks
> +++ b/test/hooks
> @@ -31,6 +31,7 @@ rm_hooks () {
>  # add a message to generate mail dir and database
>  add_message
>
> +# {pre,post}-new hooks
>  test_begin_subtest "pre-new is run"
>  rm_hooks
>  generate_message
> @@ -101,4 +102,39 @@ EOF
>  chmod +x "${HOOK_DIR}/pre-new"
>  test_expect_code 1 "hook execution failure" "notmuch new"
>
> +
> +
> +# {pre,post}-tag hooks
> +test_begin_subtest "pre-tag is run"
> +rm_hooks
> +generate_message
> +create_echo_hook "pre-tag" expected output
> +notmuch tag +foo -- '*' > /dev/null
> +test_expect_equal_file expected output
> +
> +test_begin_subtest "post-tag is run"
> +rm_hooks
> +generate_message
> +create_echo_hook "post-tag" expected output
> +notmuch tag +foo -- '*'  > /dev/null
> +test_expect_equal_file expected output
> +
> +test_begin_subtest "pre-tag is run before post-new"
> +rm_hooks
> +generate_message
> +create_echo_hook "pre-tag" pre-tag.expected pre-tag.output
> +create_echo_hook "post-tag" post-tag.expected post-tag.output
> +notmuch tag +foo -- '*'  > /dev/null
> +test_expect_equal_file post-tag.expected post-tag.output
> +
> +test_begin_subtest "pre-tag non-zero exit status (hook status)"
> +rm_hooks
> +generate_message
> +create_failing_hook "pre-tag"
> +output=`notmuch tag +foo -- '*'  2>&1`
> +test_expect_equal "$output" "Error: pre-tag hook failed with status 13"
> +
> +# depends on the previous subtest leaving broken hook behind
> +test_expect_code 1 "pre-tag non-zero exit status (notmuch status)"
"notmuch tag +foo -- '*'"
> +
>  test_done
> --
> 1.7.11.2
>
> ___
> notmuch mailing list
> notmuch at notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch
-- next part --
An HTML attachment was scrubbed...
URL: 
<http://notmuchmail.org/pipermail/notmuch/attachments/20120718/cd0913ea/attachment.html>


Re: [PATCH] cli: Hooks for tag-command

2012-07-18 Thread Jani Nikula
On Jul 18, 2012 12:25 AM, Dominik Peteler domi...@with-h.at wrote:

 hello,

 I attached some modifications which I made to notmuch. Comes with man
pages and test.


Hi Dominik, please find a couple of comments below. I'm on the road,
replying on a phone, so this is not very thorough...

BR,
Jani.

 regards

 dominik



 There are two hooks:
  * pre-tag: Run before tagging
  * post-tag: Run after

 This allows users to react on changes of tags. For example,
 you might want to move a message to a special Maildir
 depending on its notmuch tags.
 ---
  man/man1/notmuch-tag.1   | 17 +
  man/man5/notmuch-hooks.5 | 23 +++
  notmuch-tag.c| 25 +
  test/hooks   | 36 
  4 files changed, 97 insertions(+), 4 deletions(-)

 diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1
 index d810e1b..8d8b7b2 100644
 --- a/man/man1/notmuch-tag.1
 +++ b/man/man1/notmuch-tag.1
 @@ -4,6 +4,7 @@ notmuch-tag \- add/remove tags for all messages matching
the search terms

  .SH SYNOPSIS
  .B notmuch tag
 +.RB [ --no-hooks ]
  .RI  + tag |\- tag  [...] [\-\-]  search-term ...

  .SH DESCRIPTION
 @@ -29,6 +30,22 @@ updates the maildir flags according to tag changes if
the
  configuration option is enabled. See \fBnotmuch-config\fR(1) for
  details.

 +The
 +.B tag
 +command supports hooks. See  \fBnotmuch-hooks(5)\fR
 +for more details on hooks.
 +
 +Supported options for
 +.B tag
 +include
 +.RS 4
 +.TP 4
 +.BR \-\-no\-hooks
 +
 +Prevents hooks from being run.
 +.RE
 +.RE
 +
  .SH SEE ALSO

  \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
 diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5
 index b914a29..7399627 100644
 --- a/man/man5/notmuch-hooks.5
 +++ b/man/man5/notmuch-hooks.5
 @@ -38,6 +38,29 @@ the scan or import.
  Typically this hook is used to perform additional query\-based tagging
on the
  imported messages.
  .RE
 +.RS 4
 +.TP 4
 +.B pre\-tag
 +This hook is invoked by the
 +.B tag
 +command before tagging messages. If this
 +hook exits with a non-zero status, notmuch will abort further processing
of the
 +.B tag
 +command.
 +
 +Typically this hook is used for syncing the Maildir with notmuch tags.

Maildir syncing usually refers to the maildir flag and notmuch tag syncing
in the notmuch context. The above is bound to be confusing.

 +.RE
 +.RS 4
 +.TP 4
 +.B post\-tag
 +This hook is invoked by the
 +.B tag
 +command after messages have been tagged. The hook will not be run if
there have been any errors during
 +the tagging.
 +
 +Typically this hook is used for syncing the Maildir with notmuch tags.

Ditto.

 +.RE
 +

  .SH SEE ALSO

 diff --git a/notmuch-tag.c b/notmuch-tag.c
 index 7d18639..e98d3a0 100644
 --- a/notmuch-tag.c
 +++ b/notmuch-tag.c
 @@ -174,9 +174,11 @@ notmuch_tag_command (void *ctx, int argc, char
*argv[])
  int tag_ops_count = 0;
  char *query_string;
  notmuch_config_t *config;
 +const char *db_path;
  notmuch_database_t *notmuch;
  struct sigaction action;
  notmuch_bool_t synchronize_flags;
 +notmuch_bool_t run_hooks = TRUE;
  int i;
  int ret;

 @@ -198,11 +200,12 @@ notmuch_tag_command (void *ctx, int argc, char
*argv[])
  }

  for (i = 0; i  argc; i++) {
 -   if (strcmp (argv[i], --) == 0) {
 +   if (strcmp (argv[i], --no-hooks) == 0) {
 +   run_hooks = FALSE;

This is subtler than it looks. This would allow --no-hooks to be placed in
the middle of tag operations. Same for any future arguments. And it would
prevent removal of a hypothetical -no-hooks tag... I think we'll want to
support specifying -- twice to separate arguments from tag ops, and ops
from query. And it must be forbidden to mix them.

 +   } else if (strcmp (argv[i], --) == 0) {
 i++;
 break;
 -   }
 -   if (argv[i][0] == '+' || argv[i][0] == '-') {
 +   } else if (argv[i][0] == '+' || argv[i][0] == '-') {
 tag_ops[tag_ops_count].tag = argv[i] + 1;
 tag_ops[tag_ops_count].remove = (argv[i][0] == '-');
 tag_ops_count++;
 @@ -229,7 +232,15 @@ notmuch_tag_command (void *ctx, int argc, char
*argv[])
  if (config == NULL)
 return 1;

 -if (notmuch_database_open (notmuch_config_get_database_path (config),
 +db_path = notmuch_config_get_database_path (config);
 +
 +if (run_hooks) {
 +   ret = notmuch_run_hook (db_path, pre-tag);
 +   if (ret)
 +   return ret;
 +}
 +
 +if (notmuch_database_open (db_path,
NOTMUCH_DATABASE_MODE_READ_WRITE,
notmuch))
 return 1;

 @@ -239,5 +250,11 @@ notmuch_tag_command (void *ctx, int argc, char
*argv[])

  notmuch_database_destroy (notmuch);

 +if (run_hooks) {

Can't check further context atm, are you sure not to run post-tag if
there's an error?

 +   ret = notmuch_run_hook (db_path, post-tag);
 +   if 

[PATCH 5/7] go: Partially bind notmuch_database_upgrade

2012-07-18 Thread Adrien Bustany
This binding does not handle the progress callback, but at least allows
opening and upgrading a database if needed.
---
 bindings/go/src/notmuch/notmuch.go |   13 -
 1 files changed, 12 insertions(+), 1 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index d8b2739..384d5a5 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -296,7 +296,18 @@ func (self *Database) NeedsUpgrade() bool {
return true
 }
 
-// TODO: notmuch_database_upgrade
+// TODO: Proper notmuch_database_upgrade
+/* Upgrade the current database.
+ *
+ * After opening a database in read-write mode, the client should
+ * check if an upgrade is needed (notmuch_database_needs_upgrade) and
+ * if so, upgrade with this function before making any modifications.
+ */
+func (self *Database) Upgrade() Status {
+   st := Status(C.notmuch_database_upgrade(self.db, nil, nil));
+
+   return st;
+}
 
 /* Retrieve a directory object from the database for 'path'.
  *
-- 
1.7.7.6

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 4/7] go: Make Destroy functions safe to call several times

2012-07-18 Thread Adrien Bustany
Those methods were already checking if the underlying C object was NULL,
but they were not setting the pointer to NULL after destroying it.
---
 bindings/go/src/notmuch/notmuch.go |6 ++
 1 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 3f436a0..d8b2739 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -634,6 +634,7 @@ func (self *Query) SearchMessages() *Messages {
 func (self *Query) Destroy() {
if self.query != nil {
C.notmuch_query_destroy(self.query)
+   self.query = nil
}
 }
 
@@ -680,6 +681,7 @@ func (self *Threads) Valid() bool {
 func (self *Threads) Destroy() {
if self.threads != nil {
C.notmuch_threads_destroy(self.threads)
+   self.threads = nil
}
 }
 
@@ -1143,6 +1145,7 @@ func (self *Message) Destroy() {
return
}
C.notmuch_message_destroy(self.message)
+   self.message = nil
 }
 
 /* Is the given 'tags' iterator pointing at a valid tag.
@@ -1214,6 +1217,7 @@ func (self *Tags) Destroy() {
return
}
C.notmuch_tags_destroy(self.tags)
+   self.tags = nil
 }
 
 // TODO: wrap notmuch_directory_fct
@@ -1224,6 +1228,7 @@ func (self *Directory) Destroy() {
return
}
C.notmuch_directory_destroy(self.dir)
+   self.dir = nil
 }
 
 // TODO: wrap notmuch_filenames_fct
@@ -1242,6 +1247,7 @@ func (self *Filenames) Destroy() {
return
}
C.notmuch_filenames_destroy(self.fnames)
+   self.fnames = nil
 }
 
 /* EOF */
-- 
1.7.7.6

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 1/7] go: Use iota in enum bindings

2012-07-18 Thread Adrien Bustany
Using iota is the correct way to get the values in the enum increment
automatically. The old code would just set all the enum values to 0.
---
 bindings/go/src/notmuch/notmuch.go |6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 00bd53a..ecd7418 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -86,7 +86,7 @@ type Filenames struct {
 type DatabaseMode C.notmuch_database_mode_t
 
 const (
-   DATABASE_MODE_READ_ONLY DatabaseMode = 0
+   DATABASE_MODE_READ_ONLY DatabaseMode = iota
DATABASE_MODE_READ_WRITE
 )
 
@@ -386,7 +386,7 @@ func (self *Database) CreateQuery(query string) *Query {
 type Sort C.notmuch_sort_t
 
 const (
-   SORT_OLDEST_FIRST Sort = 0
+   SORT_OLDEST_FIRST Sort = iota
SORT_NEWEST_FIRST
SORT_MESSAGE_ID
SORT_UNSORTED
@@ -774,7 +774,7 @@ func (self *Message) GetFileName() string {
 type Flag C.notmuch_message_flag_t
 
 const (
-   MESSAGE_FLAG_MATCH Flag = 0
+   MESSAGE_FLAG_MATCH Flag = iota
 )
 
 /* Get a value of a flag for the email corresponding to 'message'. */
-- 
1.7.7.6

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 6/7] go: Bind notmuch_database_find_message_by_filename

2012-07-18 Thread Adrien Bustany
---
 bindings/go/src/notmuch/notmuch.go |   39 
 1 files changed, 39 insertions(+), 0 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 384d5a5..be4cb8c 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -453,6 +453,45 @@ func (self *Database) FindMessage(message_id string) 
(*Message, Status) {
return createMessage(msg, nil), st
 }
 
+/* Find a message with the given filename.
+ *
+ * If the database contains a message with the given filename then, on
+ * successful return (NOTMUCH_STATUS_SUCCESS) '*message' will be initialized to
+ * a message object. The caller should call notmuch_message_destroy when done
+ * with the message.
+ *
+ * On any failure or when the message is not found, this function initializes
+ * '*message' to NULL. This means, when NOTMUCH_STATUS_SUCCESS is returned, the
+ * caller is supposed to check '*message' for NULL to find out whether the
+ * message with the given filename is found.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successful return, check '*message'
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'message' argument is NULL
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory, creating the message object
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
+ */
+func (self *Database) FindMessageByFilename(filename string) (*Message, 
Status) {
+
+   var c_msg_filename *C.char = C.CString(filename)
+   defer C.free(unsafe.Pointer(c_msg_filename))
+
+   if c_msg_filename == nil {
+   return nil, STATUS_OUT_OF_MEMORY
+   }
+
+   var msg *C.notmuch_message_t
+   st := Status(C.notmuch_database_find_message_by_filename(self.db, 
c_msg_filename, msg))
+   if st != STATUS_SUCCESS {
+   return nil, st
+   }
+   return createMessage(msg, nil), st
+}
+
 /* Return a list of all tags found in the database.
  *
  * This function creates a list of all tags found in the database. The
-- 
1.7.7.6

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 2/7] go: Add missing MESSAGE_FLAG_EXCLUDED in Flag enum

2012-07-18 Thread Adrien Bustany
---
 bindings/go/src/notmuch/notmuch.go |1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index ecd7418..1d77fd2 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -775,6 +775,7 @@ type Flag C.notmuch_message_flag_t
 
 const (
MESSAGE_FLAG_MATCH Flag = iota
+   MESSAGE_FLAG_EXCLUDED
 )
 
 /* Get a value of a flag for the email corresponding to 'message'. */
-- 
1.7.7.6

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 0/7] Various fixes for the Go bindings

2012-07-18 Thread Adrien Bustany
The following patches fix some serious memory management issues with
the Go bindings, and add some missing functions as well.

Adrien Bustany (7):
  go: Use iota in enum bindings
  go: Add missing MESSAGE_FLAG_EXCLUDED in Flag enum
  go: Allow notmuch objects to be garbage collected
  go: Make Destroy functions safe to call several times
  go: Partially bind notmuch_database_upgrade
  go: Bind notmuch_database_find_message_by_filename
  go: Bind notmuch_thread_t functions

 bindings/go/src/notmuch/notmuch.go |  471 ++--
 1 files changed, 447 insertions(+), 24 deletions(-)

-- 
1.7.7.6

___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


[PATCH 7/7] go: Bind notmuch_thread_t functions

2012-07-18 Thread Adrien Bustany
---
 bindings/go/src/notmuch/notmuch.go |  253 +++-
 1 files changed, 252 insertions(+), 1 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index be4cb8c..f667dbb 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -12,6 +12,8 @@ package notmuch
 */
 import C
 import runtime
+import strings
+import time
 import unsafe
 
 // Status codes used for the return values of most functions
@@ -700,7 +702,20 @@ func (self *Query) CountMessages() uint {
return uint(C.notmuch_query_count_messages(self.query))
 }
 
-// TODO: wrap threads and thread
+/* Return the number of threads matching a search.
+ *
+ * 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
+ * notmuch_query_count_messages().
+ *
+ * If an error occurs, this function may return 0.
+ */
+func (self *Query) CountThreads() uint {
+   return uint(C.notmuch_query_count_threads(self.query))
+}
 
 /* Is the given 'threads' iterator pointing at a valid thread.
  *
@@ -722,6 +737,45 @@ func (self *Threads) Valid() bool {
return true
 }
 
+/* Get the current thread from 'threads' as a notmuch_thread_t.
+ *
+ * Note: The returned thread belongs to 'threads' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+func (self *Threads) Get() *Thread {
+   if self.threads == nil {
+   return nil
+   }
+   thread := C.notmuch_threads_get(self.threads)
+   if thread == nil {
+   return nil
+   }
+   return createThread(thread, self)
+}
+
+/* Move the 'threads' iterator to the next thread.
+ *
+ * If 'threads' is already pointing at the last thread then the
+ * iterator will be moved to a point just beyond that last thread,
+ * (where notmuch_threads_valid will return FALSE and
+ * notmuch_threads_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+func (self *Threads) MoveToNext() {
+   if self.threads == nil {
+   return
+   }
+   C.notmuch_threads_move_to_next(self.threads)
+}
+
 /* Destroy a notmuch_threads_t object.
  *
  * It's not strictly necessary to call this function. All memory from
@@ -735,6 +789,203 @@ func (self *Threads) Destroy() {
}
 }
 
+/* Get the thread ID of 'thread'.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+func (self *Thread) GetThreadId() string {
+   if self.thread == nil {
+   return 
+   }
+   id := C.notmuch_thread_get_thread_id(self.thread)
+
+   if id == nil {
+   return 
+   }
+
+   return C.GoString(id)
+}
+
+/* Get the total number of messages in 'thread'.
+ *
+ * This count consists of all messages in the database belonging to
+ * this thread. Contrast with notmuch_thread_get_matched_messages() .
+ */
+func (self *Thread) GetTotalMessages() int {
+   if self.thread == nil {
+   return 0
+   }
+   return int(C.notmuch_thread_get_total_messages(self.thread))
+}
+
+/* Get a notmuch_messages_t iterator for the top-level messages in
+ * 'thread'.
+ *
+ * This iterator will not necessarily iterate over all of the messages
+ * in the thread. It will only iterate over the messages in the thread
+ * which are not replies to other messages in the thread.
+ *
+ * To iterate over all messages in the thread, the caller will need to
+ * iterate over the result of notmuch_message_get_replies for each
+ * top-level message (and do that recursively for the resulting
+ * messages, etc.).
+ */
+func (self *Thread) GetToplevelMessages() *Messages {
+   if self.thread == nil {
+   return nil
+   }
+   msgs := C.notmuch_thread_get_toplevel_messages(self.thread)
+   if msgs == nil {
+   return nil
+   }
+   return createMessages(msgs, self)
+}
+
+/* Get a notmuch_messages_t iterator for the top-level messages in
+ * 'thread'.
+ *
+ * This iterator will not necessarily iterate over all of the messages
+ * in the thread. It will only iterate over the messages in the thread
+ * which are not replies to other messages in the thread.
+ *
+ * To iterate over all messages in the thread, the caller will need to
+ * iterate over the result of notmuch_message_get_replies 

[PATCH 3/7] go: Allow notmuch objects to be garbage collected

2012-07-18 Thread Adrien Bustany
This makes notmuch appropriately free the underlying notmuch C objects
when garbage collecting their Go wrappers. To make sure we don't break
the underlying links between objects (for example, a notmuch_messages_t
being GC'ed before a notmuch_message_t belonging to it), we add for each
wraper struct a pointer to the owner object (Go objects with a reference
pointing to them don't get garbage collected).
---
 bindings/go/src/notmuch/notmuch.go |  153 +++-
 1 files changed, 134 insertions(+), 19 deletions(-)

diff --git a/bindings/go/src/notmuch/notmuch.go 
b/bindings/go/src/notmuch/notmuch.go
index 1d77fd2..3f436a0 100644
--- a/bindings/go/src/notmuch/notmuch.go
+++ b/bindings/go/src/notmuch/notmuch.go
@@ -11,6 +11,7 @@ package notmuch
 #include notmuch.h
 */
 import C
+import runtime
 import unsafe
 
 // Status codes used for the return values of most functions
@@ -47,40 +48,152 @@ func (self Status) String() string {
 /* Various opaque data types. For each notmuch_foo_t see the various
  * notmuch_foo functions below. */
 
+type Object interface {}
+
 type Database struct {
db *C.notmuch_database_t
 }
 
+func createDatabase(db *C.notmuch_database_t) *Database {
+   self := Database{db: db}
+
+   runtime.SetFinalizer(self, func(x *Database) {
+   if (x.db != nil) {
+   C.notmuch_database_destroy(x.db)
+   }
+   })
+
+   return self
+}
+
 type Query struct {
query *C.notmuch_query_t
+   owner Object
+}
+
+func createQuery(query *C.notmuch_query_t, owner Object) *Query {
+   self := Query{query: query, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Query) {
+   if (x.query != nil) {
+   C.notmuch_query_destroy(x.query)
+   }
+   })
+
+   return self
 }
 
 type Threads struct {
threads *C.notmuch_threads_t
+   owner Object
+}
+
+func createThreads(threads *C.notmuch_threads_t, owner Object) *Threads {
+   self := Threads{threads: threads, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Threads) {
+   if (x.threads != nil) {
+   C.notmuch_threads_destroy(x.threads)
+   }
+   })
+
+   return self
 }
 
 type Thread struct {
thread *C.notmuch_thread_t
+   owner Object
+}
+
+func createThread(thread *C.notmuch_thread_t, owner Object) *Thread {
+   self := Thread{thread: thread, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Thread) {
+   if (x.thread != nil) {
+   C.notmuch_thread_destroy(x.thread)
+   }
+   })
+
+   return self
 }
 
 type Messages struct {
messages *C.notmuch_messages_t
+   owner Object
+}
+
+func createMessages(messages *C.notmuch_messages_t, owner Object) *Messages {
+   self := Messages{messages: messages, owner: owner}
+
+   return self
 }
 
 type Message struct {
message *C.notmuch_message_t
+   owner Object
+}
+
+func createMessage(message *C.notmuch_message_t, owner Object) *Message {
+   self := Message{message: message, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Message) {
+   if (x.message != nil) {
+   C.notmuch_message_destroy(x.message)
+   }
+   })
+
+   return self
 }
 
 type Tags struct {
tags *C.notmuch_tags_t
+   owner Object
+}
+
+func createTags(tags *C.notmuch_tags_t, owner Object) *Tags {
+   self := Tags{tags: tags, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Tags) {
+   if (x.tags != nil) {
+   C.notmuch_tags_destroy(x.tags)
+   }
+   })
+
+   return self
 }
 
 type Directory struct {
dir *C.notmuch_directory_t
+   owner Object
+}
+
+func createDirectory(directory *C.notmuch_directory_t, owner Object) 
*Directory {
+   self := Directory{dir: directory, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Directory) {
+   if (x.dir != nil) {
+   C.notmuch_directory_destroy(x.dir)
+   }
+   })
+
+   return self
 }
 
 type Filenames struct {
fnames *C.notmuch_filenames_t
+   owner Object
+}
+
+func createFilenames(filenames *C.notmuch_filenames_t, owner Object) 
*Filenames {
+   self := Filenames{fnames: filenames, owner: owner}
+
+   runtime.SetFinalizer(self, func(x *Filenames) {
+   if (x.fnames != nil) {
+   C.notmuch_filenames_destroy(x.fnames)
+   }
+   })
+
+   return self
 }
 
 type DatabaseMode C.notmuch_database_mode_t
@@ -100,12 +213,13 @@ func NewDatabase(path string) (*Database, Status) {
return nil, STATUS_OUT_OF_MEMORY
}
 
-   self := Database{db: nil}
-   st := Status(C.notmuch_database_create(c_path, self.db))
+   var db *C.notmuch_database_t;
+   st := 

Re: [PATCH v6 2/3] Add structured output formatter for JSON and plain text.

2012-07-18 Thread Austin Clements
Quoth cra...@gmx.net on Jul 16 at 10:35 am:
 Using the new structured printer support in sprinter.h, implement
 sprinter_json_create, which returns a new JSON structured output
 formatter. The formatter prints output similar to the existing JSON, but
 with differences in whitespace (mostly newlines, --output=summary prints
 the entire message summary on one line, not split across multiple lines).
 
 Also implement a structured formatter for plain text that prints
 prefixed strings, to be used with notmuch-search.c plain text output.
 ---
  Makefile.local |   2 +
  sprinter-json.c| 191 
 +
  sprinter-text-search.c | 146 +
  sprinter.h |   9 +++
  4 files changed, 348 insertions(+)
  create mode 100644 sprinter-json.c
  create mode 100644 sprinter-text-search.c
 
 diff --git a/Makefile.local b/Makefile.local
 index a890df2..4f534f1 100644
 --- a/Makefile.local
 +++ b/Makefile.local
 @@ -290,6 +290,8 @@ notmuch_client_srcs = \
   notmuch-show.c  \
   notmuch-tag.c   \
   notmuch-time.c  \
 + sprinter-text-search.c  \
 + sprinter-json.c \
   query-string.c  \
   mime-node.c \
   crypto.c\
 diff --git a/sprinter-json.c b/sprinter-json.c
 new file mode 100644
 index 000..a93a390
 --- /dev/null
 +++ b/sprinter-json.c
 @@ -0,0 +1,191 @@
 +#include stdbool.h
 +#include stdio.h
 +#include talloc.h
 +#include sprinter.h
 +
 +struct sprinter_json {
 +struct sprinter vtable;
 +FILE *stream;
 +/* Top of the state stack, or NULL if the printer is not currently
 + * inside any aggregate types. */
 +struct json_state *state;
 +
 +/* A flag to signify that a separator should be inserted in the
 + * output as soon as possible.
 + */
 +notmuch_bool_t insert_separator;
 +};
 +
 +struct json_state {
 +struct json_state *parent;
 +/* True if nothing has been printed in this aggregate yet.
 + * Suppresses the comma before a value. */
 +notmuch_bool_t first;
 +/* The character that closes the current aggregate. */
 +char close;
 +};
 +
 +/* Helper function to set up the stream to print a value.  If this
 + * value follows another value, prints a comma. */
 +static struct sprinter_json *
 +json_begin_value (struct sprinter *sp)
 +{
 +struct sprinter_json *spj = (struct sprinter_json *) sp;
 +
 +if (spj-state) {
 + if (! spj-state-first) {
 + fputc (',', spj-stream);
 + if (spj-insert_separator) {
 + fputc ('\n', spj-stream);
 + spj-insert_separator = FALSE;
 + } else
 + fputc (' ', spj-stream);
 + } else
 + spj-state-first = FALSE;
 +}
 +return spj;
 +}
 +
 +/* Helper function to begin an aggregate type.  Prints the open
 + * character and pushes a new state frame. */
 +static void
 +json_begin_aggregate (struct sprinter *sp, char open, char close)
 +{
 +struct sprinter_json *spj = json_begin_value (sp);
 +struct json_state *state = talloc (spj, struct json_state);
 +
 +fputc (open, spj-stream);
 +state-parent = spj-state;
 +state-first = TRUE;
 +state-close = close;
 +spj-state = state;
 +}
 +
 +static void
 +json_begin_map (struct sprinter *sp)
 +{
 +json_begin_aggregate (sp, '{', '}');
 +}
 +
 +static void
 +json_begin_list (struct sprinter *sp)
 +{
 +json_begin_aggregate (sp, '[', ']');
 +}
 +
 +static void
 +json_end (struct sprinter *sp)
 +{
 +struct sprinter_json *spj = (struct sprinter_json *) sp;
 +struct json_state *state = spj-state;
 +
 +fputc (spj-state-close, spj-stream);
 +spj-state = state-parent;
 +talloc_free (state);
 +if (spj-state == NULL)
 + fputc ('\n', spj-stream);
 +}
 +
 +static void
 +json_string (struct sprinter *sp, const char *val)
 +{
 +static const char *const escapes[] = {
 + ['\'] = \\\, ['\\'] = , ['\b'] = \\b,
 + ['\f'] = \\f,  ['\n'] = \\n,  ['\t'] = \\t
 +};
 +struct sprinter_json *spj = json_begin_value (sp);
 +
 +fputc ('', spj-stream);
 +for (; *val; ++val) {
 + unsigned char ch = *val;
 + if (ch  ARRAY_SIZE (escapes)  escapes[ch])
 + fputs (escapes[ch], spj-stream);
 + else if (ch = 32)
 + fputc (ch, spj-stream);
 + else
 + fprintf (spj-stream, \\u%04x, ch);
 +}
 +fputc ('', spj-stream);
 +}
 +
 +static void
 +json_integer (struct sprinter *sp, int val)
 +{
 +struct sprinter_json *spj = json_begin_value (sp);
 +
 +fprintf (spj-stream, %d, val);
 +}
 +
 +static void
 +json_boolean (struct sprinter *sp, notmuch_bool_t val)
 +{
 +struct sprinter_json *spj = json_begin_value (sp);
 +
 +fputs (val ? true : false, spj-stream);
 +}
 +
 +static void
 +json_null (struct sprinter *sp)
 +{
 +struct sprinter_json *spj = json_begin_value (sp);
 +
 +fputs (null, 

Re: [PATCH v6 3/3] Use the structured formatters in notmuch-search.c.

2012-07-18 Thread Austin Clements
Just a few comments (don't forget to scroll all the way down).
Overall this is looking pretty good.

Quoth cra...@gmx.net on Jul 16 at 10:35 am:
 This patch switches from the current ad-hoc printer to the structured
 formatters in sprinter.h, sprinter-text-search.c and sprinter-json.c.
 
 The JSON tests are changed slightly in order to make them PASS for the
 new structured output formatter.
 
 The text tests pass without adaptation.
 ---
  notmuch-search.c | 300 
 ---
  test/json|  18 +---
  2 files changed, 86 insertions(+), 232 deletions(-)

That's a fantastic diffstat.

 
 diff --git a/notmuch-search.c b/notmuch-search.c
 index 3be296d..cf927e6 100644
 --- a/notmuch-search.c
 +++ b/notmuch-search.c
 @@ -19,6 +19,7 @@
   */
  
  #include notmuch-client.h
 +#include sprinter.h
  
  typedef enum {
  OUTPUT_SUMMARY,
 @@ -28,92 +29,6 @@ typedef enum {
  OUTPUT_TAGS
  } output_t;
  
 -typedef struct search_format {
 -const char *results_start;
 -const char *item_start;
 -void (*item_id) (const void *ctx,
 -  const char *item_type,
 -  const char *item_id);
 -void (*thread_summary) (const void *ctx,
 - const char *thread_id,
 - const time_t date,
 - const int matched,
 - const int total,
 - const char *authors,
 - const char *subject);
 -const char *tag_start;
 -const char *tag;
 -const char *tag_sep;
 -const char *tag_end;
 -const char *item_sep;
 -const char *item_end;
 -const char *results_end;
 -const char *results_null;
 -} search_format_t;
 -
 -static void
 -format_item_id_text (const void *ctx,
 -  const char *item_type,
 -  const char *item_id);
 -
 -static void
 -format_thread_text (const void *ctx,
 - const char *thread_id,
 - const time_t date,
 - const int matched,
 - const int total,
 - const char *authors,
 - const char *subject);
 -static const search_format_t format_text = {
 -,
 - ,
 - format_item_id_text,
 - format_thread_text,
 -  (,
 - %s,  ,
 - ), \n,
 - ,
 -\n,
 -,
 -};
 -
 -static void
 -format_item_id_json (const void *ctx,
 -  const char *item_type,
 -  const char *item_id);
 -
 -static void
 -format_thread_json (const void *ctx,
 - const char *thread_id,
 - const time_t date,
 - const int matched,
 - 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 = {
 -[,
 - {,
 - format_item_id_json,
 - format_thread_json,
 - \tags\: [,
 - \%s\, , ,
 - ], ,\n,
 - },
 -]\n,
 -]\n,
 -};
 -
 -static void
 -format_item_id_text (unused (const void *ctx),
 -  const char *item_type,
 -  const char *item_id)
 -{
 -printf (%s%s, item_type, item_id);
 -}
 -
  static char *
  sanitize_string (const void *ctx, const char *str)
  {
 @@ -131,72 +46,8 @@ sanitize_string (const void *ctx, const char *str)
  return out;
  }
  
 -static void
 -format_thread_text (const void *ctx,
 - const char *thread_id,
 - const time_t date,
 - const int matched,
 - const int total,
 - const char *authors,
 - const char *subject)
 -{
 -void *ctx_quote = talloc_new (ctx);
 -
 -printf (thread:%s %12s [%d/%d] %s; %s,
 - thread_id,
 - notmuch_time_relative_date (ctx, date),
 - matched,
 - total,
 - sanitize_string (ctx_quote, authors),
 - sanitize_string (ctx_quote, subject));
 -
 -talloc_free (ctx_quote);
 -}
 -
 -static void
 -format_item_id_json (const void *ctx,
 -  unused (const char *item_type),
 -  const char *item_id)
 -{
 -void *ctx_quote = talloc_new (ctx);
 -
 -printf (%s, json_quote_str (ctx_quote, item_id));
 -
 -talloc_free (ctx_quote);
 -
 -}
 -
 -static void
 -format_thread_json (const void *ctx,
 - const char *thread_id,
 - const time_t date,
 - const int matched,
 - const int total,
 - const char *authors,
 - const char *subject)
 -{
 -void *ctx_quote = talloc_new (ctx);
 -
 -printf (\thread\: %s,\n
 - \timestamp\: %ld,\n
 - \date_relative\: \%s\,\n
 - \matched\: %d,\n
 - \total\: %d,\n
 - \authors\: %s,\n
 - 

[PATCHv2] cli: Hooks for tag-command

2012-07-18 Thread Dominik Peteler
hello,

I improved my patch according to Janis mail:
 * new cli syntax: notmuch tag [ --no-hooks ] -- tag ops [ -- ] search terms
 * adjusted man pages and wrote tests

I had the idea to improve this feature by passing the message-ids or the 
filename to the hooks.
What's your opinion about that ? Any suggestions ?

regards

dominik



There are two hooks:
 * pre-tag: Run before tagging
 * post-tag: Run after

This allows users to react on changes of tags. For example,
you might want to move a message to a special Maildir
depending on its notmuch tags.
---
 man/man1/notmuch-tag.1   | 22 +++-
 man/man5/notmuch-hooks.5 | 19 ++
 notmuch-tag.c| 52 +---
 test/hooks   | 36 +
 test/tagging | 28 ++
 5 files changed, 153 insertions(+), 4 deletions(-)

diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1
index d810e1b..e00e189 100644
--- a/man/man1/notmuch-tag.1
+++ b/man/man1/notmuch-tag.1
@@ -4,7 +4,11 @@ notmuch-tag \- add/remove tags for all messages matching the 
search terms
 
 .SH SYNOPSIS
 .B notmuch tag
-.RI  + tag |\- tag  [...] [\-\-]  search-term ...
+.RI  + tag  |\- tag  [...] [\-\-]  search-term ...
+
+.B notmuch tag
+.RB [ --no-hooks ]
+.RI \-\- + tag  |\- tag  [...] \-\-  search-term ...
 
 .SH DESCRIPTION
 
@@ -29,6 +33,22 @@ updates the maildir flags according to tag changes if the
 configuration option is enabled. See \fBnotmuch-config\fR(1) for
 details.
 
+The
+.B tag
+command supports hooks. See  \fBnotmuch-hooks(5)\fR
+for more details on hooks.
+
+Supported options for
+.B tag
+include
+.RS 4
+.TP 4
+.BR \-\-no\-hooks
+
+Prevents hooks from being run.
+.RE
+.RE
+
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5
index b914a29..e193ef5 100644
--- a/man/man5/notmuch-hooks.5
+++ b/man/man5/notmuch-hooks.5
@@ -38,6 +38,25 @@ the scan or import.
 Typically this hook is used to perform additional query\-based tagging on the
 imported messages.
 .RE
+.RS 4
+.TP 4
+.B pre\-tag
+This hook is invoked by the
+.B tag
+command before tagging messages. If this
+hook exits with a non-zero status, notmuch will abort further processing of the
+.B tag
+command.
+.RE
+.RS 4
+.TP 4
+.B post\-tag
+This hook is invoked by the
+.B tag
+command after messages have been tagged. The hook will not be run if there 
have been any errors during
+the tagging.
+.RE
+
 
 .SH SEE ALSO
 
diff --git a/notmuch-tag.c b/notmuch-tag.c
index 7d18639..7572059 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -174,9 +174,17 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 int tag_ops_count = 0;
 char *query_string;
 notmuch_config_t *config;
+const char *db_path;
 notmuch_database_t *notmuch;
 struct sigaction action;
 notmuch_bool_t synchronize_flags;
+/* Points to the position of the -- delimiters, e. g.
+ *optional arguments arg_delimiters[0] tag ops arg_delimiters[1] 
search terms
+ *
+ * arg_delimiters[0] may remain -1 if there are no arguments given
+ * arg_delimiters[0] may remain -1 if there is no delimiter between tag 
ops and search terms */
+int arg_delimiters[2] = {-1, -1};
+notmuch_bool_t run_hooks = TRUE;
 int i;
 int ret;
 
@@ -197,11 +205,37 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
return 1;
 }
 
+/* Determine position of delimiters */
 for (i = 0; i  argc; i++) {
if (strcmp (argv[i], --) == 0) {
-   i++;
-   break;
+   if (arg_delimiters[1] == -1) {
+   arg_delimiters[1] = i;
+   } else if (arg_delimiters[0] == -1) {
+   arg_delimiters[0] = arg_delimiters[1];
+   arg_delimiters[1] = i;
+   } else {
+   fprintf (stderr, Error: 'notmuch tag' requires delimiter 
\--\ at most two times.\n);
+   return 1;
+   }
}
+}
+
+/* Process arguments if present */
+for (i = 0; i  arg_delimiters[0]; i++) {
+   if (strcmp (argv[i], --no-hooks) == 0) {
+   run_hooks = FALSE;
+   } else {
+   fprintf (stderr, Error: 'notmuch tag' doesn't recognize argument 
'%s'.\n, argv[i]);
+   return 1;
+   }
+}
+
+/* Set arg_delimiters[1] to argc if no delimiters at all are present */
+if (arg_delimiters[1] == -1)
+   arg_delimiters[1] = argc;
+
+/* Read tag ops */
+for (i = arg_delimiters[0]+1; i  arg_delimiters[1]; i++) {
if (argv[i][0] == '+' || argv[i][0] == '-') {
tag_ops[tag_ops_count].tag = argv[i] + 1;
tag_ops[tag_ops_count].remove = (argv[i][0] == '-');
@@ -229,7 +263,15 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])
 if (config == NULL)
return 1;
 
-if (notmuch_database_open 

Re: [PATCH 1/7] go: Use iota in enum bindings

2012-07-18 Thread Austin Clements
Hah.  I guess nobody has tried to modify a notmuch database using the
Go bindings.

Could this instead assign the constants to
C.NOTMUCH_DATABASE_MODE_READ_ONLY, etc, rather than duplicating their
values?  It would be nice to do that for the Status values as well
(which are correctly using iota, at least).

Quoth Adrien Bustany on Jul 18 at  9:34 pm:
 Using iota is the correct way to get the values in the enum increment
 automatically. The old code would just set all the enum values to 0.
 ---
  bindings/go/src/notmuch/notmuch.go |6 +++---
  1 files changed, 3 insertions(+), 3 deletions(-)
 
 diff --git a/bindings/go/src/notmuch/notmuch.go 
 b/bindings/go/src/notmuch/notmuch.go
 index 00bd53a..ecd7418 100644
 --- a/bindings/go/src/notmuch/notmuch.go
 +++ b/bindings/go/src/notmuch/notmuch.go
 @@ -86,7 +86,7 @@ type Filenames struct {
  type DatabaseMode C.notmuch_database_mode_t
  
  const (
 - DATABASE_MODE_READ_ONLY DatabaseMode = 0
 + DATABASE_MODE_READ_ONLY DatabaseMode = iota
   DATABASE_MODE_READ_WRITE
  )
  
 @@ -386,7 +386,7 @@ func (self *Database) CreateQuery(query string) *Query {
  type Sort C.notmuch_sort_t
  
  const (
 - SORT_OLDEST_FIRST Sort = 0
 + SORT_OLDEST_FIRST Sort = iota
   SORT_NEWEST_FIRST
   SORT_MESSAGE_ID
   SORT_UNSORTED
 @@ -774,7 +774,7 @@ func (self *Message) GetFileName() string {
  type Flag C.notmuch_message_flag_t
  
  const (
 - MESSAGE_FLAG_MATCH Flag = 0
 + MESSAGE_FLAG_MATCH Flag = iota
  )
  
  /* Get a value of a flag for the email corresponding to 'message'. */
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [PATCH 1/7] go: Use iota in enum bindings

2012-07-18 Thread Sebastien Binet
Austin Clements amdra...@mit.edu writes:

 Hah.  I guess nobody has tried to modify a notmuch database using the
 Go bindings.

 Could this instead assign the constants to
 C.NOTMUCH_DATABASE_MODE_READ_ONLY, etc, rather than duplicating their
 values?  It would be nice to do that for the Status values as well
 (which are correctly using iota, at least).

yep.
I have a few fixes like that in my repo:
https://github.com/sbinet/notmuch/commits/dev/go-bindings/bindings/go/src/notmuch

https://github.com/sbinet/notmuch/commit/38713de1b90a29b66b2a7f310065a3ffcf527d9b#L1L88

-s


pgpXK4y5mdBMb.pgp
Description: PGP signature
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [PATCH 3/7] go: Allow notmuch objects to be garbage collected

2012-07-18 Thread Austin Clements
This is subtle enough that I think it deserves a comment in the source
code explaining that tracking the talloc owner reference, combined
with the fact that Go finalizers are run in dependency order, ensures
that the C objects will always be destroyed from the talloc leaves up.

Just one inline comment below.  Otherwise, I think this is all
correct.

Is reproducing the talloc hierarchy in all of the bindings really the
right approach?  I feel like there has to be a better way (or that the
way we use talloc in the library is slightly broken).  What if the
bindings created an additional talloc reference to each managed
object, just to keep the object alive, and used talloc_unlink instead
of the destroy functions?

Quoth Adrien Bustany on Jul 18 at  9:34 pm:
 This makes notmuch appropriately free the underlying notmuch C objects
 when garbage collecting their Go wrappers. To make sure we don't break
 the underlying links between objects (for example, a notmuch_messages_t
 being GC'ed before a notmuch_message_t belonging to it), we add for each
 wraper struct a pointer to the owner object (Go objects with a reference
 pointing to them don't get garbage collected).
 ---
  bindings/go/src/notmuch/notmuch.go |  153 
 +++-
  1 files changed, 134 insertions(+), 19 deletions(-)
 
 diff --git a/bindings/go/src/notmuch/notmuch.go 
 b/bindings/go/src/notmuch/notmuch.go
 index 1d77fd2..3f436a0 100644
 --- a/bindings/go/src/notmuch/notmuch.go
 +++ b/bindings/go/src/notmuch/notmuch.go
 @@ -11,6 +11,7 @@ package notmuch
  #include notmuch.h
  */
  import C
 +import runtime
  import unsafe
  
  // Status codes used for the return values of most functions
 @@ -47,40 +48,152 @@ func (self Status) String() string {
  /* Various opaque data types. For each notmuch_foo_t see the various
   * notmuch_foo functions below. */
  
 +type Object interface {}
 +
  type Database struct {
   db *C.notmuch_database_t
  }
  
 +func createDatabase(db *C.notmuch_database_t) *Database {
 + self := Database{db: db}
 +
 + runtime.SetFinalizer(self, func(x *Database) {
 + if (x.db != nil) {
 + C.notmuch_database_destroy(x.db)
 + }
 + })
 +
 + return self
 +}
 +
  type Query struct {
   query *C.notmuch_query_t
 + owner Object
 +}
 +
 +func createQuery(query *C.notmuch_query_t, owner Object) *Query {
 + self := Query{query: query, owner: owner}
 +
 + runtime.SetFinalizer(self, func(x *Query) {
 + if (x.query != nil) {
 + C.notmuch_query_destroy(x.query)
 + }
 + })
 +
 + return self
  }
  
  type Threads struct {
   threads *C.notmuch_threads_t
 + owner Object
 +}
 +
 +func createThreads(threads *C.notmuch_threads_t, owner Object) *Threads {
 + self := Threads{threads: threads, owner: owner}
 +
 + runtime.SetFinalizer(self, func(x *Threads) {
 + if (x.threads != nil) {
 + C.notmuch_threads_destroy(x.threads)
 + }
 + })
 +
 + return self
  }
  
  type Thread struct {
   thread *C.notmuch_thread_t
 + owner Object
 +}
 +
 +func createThread(thread *C.notmuch_thread_t, owner Object) *Thread {
 + self := Thread{thread: thread, owner: owner}
 +
 + runtime.SetFinalizer(self, func(x *Thread) {
 + if (x.thread != nil) {
 + C.notmuch_thread_destroy(x.thread)
 + }
 + })
 +
 + return self
  }
  
  type Messages struct {
   messages *C.notmuch_messages_t
 + owner Object
 +}
 +
 +func createMessages(messages *C.notmuch_messages_t, owner Object) *Messages {
 + self := Messages{messages: messages, owner: owner}
 +
 + return self
  }
  
  type Message struct {
   message *C.notmuch_message_t
 + owner Object
 +}
 +
 +func createMessage(message *C.notmuch_message_t, owner Object) *Message {
 + self := Message{message: message, owner: owner}
 +
 + runtime.SetFinalizer(self, func(x *Message) {
 + if (x.message != nil) {
 + C.notmuch_message_destroy(x.message)
 + }
 + })
 +
 + return self
  }
  
  type Tags struct {
   tags *C.notmuch_tags_t
 + owner Object
 +}
 +
 +func createTags(tags *C.notmuch_tags_t, owner Object) *Tags {
 + self := Tags{tags: tags, owner: owner}
 +
 + runtime.SetFinalizer(self, func(x *Tags) {
 + if (x.tags != nil) {
 + C.notmuch_tags_destroy(x.tags)
 + }
 + })
 +
 + return self
  }
  
  type Directory struct {
   dir *C.notmuch_directory_t
 + owner Object
 +}
 +
 +func createDirectory(directory *C.notmuch_directory_t, owner Object) 
 *Directory {
 + self := Directory{dir: directory, owner: owner}
 +
 + runtime.SetFinalizer(self, func(x *Directory) {
 + if (x.dir != nil) {
 + C.notmuch_directory_destroy(x.dir)
 + }
 + })
 +
 + return 

Re: [PATCH 5/7] go: Partially bind notmuch_database_upgrade

2012-07-18 Thread Austin Clements
Quoth Adrien Bustany on Jul 18 at  9:34 pm:
 This binding does not handle the progress callback, but at least allows
 opening and upgrading a database if needed.
 ---
  bindings/go/src/notmuch/notmuch.go |   13 -
  1 files changed, 12 insertions(+), 1 deletions(-)
 
 diff --git a/bindings/go/src/notmuch/notmuch.go 
 b/bindings/go/src/notmuch/notmuch.go
 index d8b2739..384d5a5 100644
 --- a/bindings/go/src/notmuch/notmuch.go
 +++ b/bindings/go/src/notmuch/notmuch.go
 @@ -296,7 +296,18 @@ func (self *Database) NeedsUpgrade() bool {
   return true
  }
  
 -// TODO: notmuch_database_upgrade
 +// TODO: Proper notmuch_database_upgrade
 +/* Upgrade the current database.
 + *
 + * After opening a database in read-write mode, the client should
 + * check if an upgrade is needed (notmuch_database_needs_upgrade) and
 + * if so, upgrade with this function before making any modifications.
 + */
 +func (self *Database) Upgrade() Status {
 + st := Status(C.notmuch_database_upgrade(self.db, nil, nil));
 +
 + return st;

*gasp*  Semicolons!

 +}
  
  /* Retrieve a directory object from the database for 'path'.
   *
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [PATCH 7/7] go: Bind notmuch_thread_t functions

2012-07-18 Thread Austin Clements
Quoth Adrien Bustany on Jul 18 at  9:34 pm:
 ---
  bindings/go/src/notmuch/notmuch.go |  253 
 +++-
  1 files changed, 252 insertions(+), 1 deletions(-)
 
 diff --git a/bindings/go/src/notmuch/notmuch.go 
 b/bindings/go/src/notmuch/notmuch.go
 index be4cb8c..f667dbb 100644
 --- a/bindings/go/src/notmuch/notmuch.go
 +++ b/bindings/go/src/notmuch/notmuch.go
 @@ -12,6 +12,8 @@ package notmuch
  */
  import C
  import runtime
 +import strings
 +import time
  import unsafe
  
  // Status codes used for the return values of most functions
 @@ -700,7 +702,20 @@ func (self *Query) CountMessages() uint {
   return uint(C.notmuch_query_count_messages(self.query))
  }
  
 -// TODO: wrap threads and thread
 +/* Return the number of threads matching a search.
 + *
 + * 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
 + * notmuch_query_count_messages().
 + *
 + * If an error occurs, this function may return 0.
 + */
 +func (self *Query) CountThreads() uint {
 + return uint(C.notmuch_query_count_threads(self.query))
 +}
  
  /* Is the given 'threads' iterator pointing at a valid thread.
   *
 @@ -722,6 +737,45 @@ func (self *Threads) Valid() bool {
   return true
  }
  
 +/* Get the current thread from 'threads' as a notmuch_thread_t.
 + *
 + * Note: The returned thread belongs to 'threads' and has a lifetime
 + * identical to it (and the query to which it belongs).
 + *
 + * See the documentation of notmuch_query_search_threads for example
 + * code showing how to iterate over a notmuch_threads_t object.
 + *
 + * If an out-of-memory situation occurs, this function will return
 + * NULL.
 + */
 +func (self *Threads) Get() *Thread {
 + if self.threads == nil {
 + return nil
 + }
 + thread := C.notmuch_threads_get(self.threads)
 + if thread == nil {
 + return nil
 + }
 + return createThread(thread, self)
 +}
 +
 +/* Move the 'threads' iterator to the next thread.
 + *
 + * If 'threads' is already pointing at the last thread then the
 + * iterator will be moved to a point just beyond that last thread,
 + * (where notmuch_threads_valid will return FALSE and
 + * notmuch_threads_get will return NULL).
 + *
 + * See the documentation of notmuch_query_search_threads for example
 + * code showing how to iterate over a notmuch_threads_t object.
 + */
 +func (self *Threads) MoveToNext() {
 + if self.threads == nil {
 + return
 + }
 + C.notmuch_threads_move_to_next(self.threads)
 +}
 +
  /* Destroy a notmuch_threads_t object.
   *
   * It's not strictly necessary to call this function. All memory from
 @@ -735,6 +789,203 @@ func (self *Threads) Destroy() {
   }
  }
  
 +/* Get the thread ID of 'thread'.
 + *
 + * The returned string belongs to 'thread' and as such, should not be
 + * modified by the caller and will only be valid for as long as the
 + * thread is valid, (which is until notmuch_thread_destroy or until
 + * the query from which it derived is destroyed).
 + */
 +func (self *Thread) GetThreadId() string {
 + if self.thread == nil {
 + return 
 + }
 + id := C.notmuch_thread_get_thread_id(self.thread)
 +
 + if id == nil {
 + return 
 + }
 +
 + return C.GoString(id)
 +}
 +
 +/* Get the total number of messages in 'thread'.
 + *
 + * This count consists of all messages in the database belonging to
 + * this thread. Contrast with notmuch_thread_get_matched_messages() .
 + */
 +func (self *Thread) GetTotalMessages() int {
 + if self.thread == nil {
 + return 0
 + }
 + return int(C.notmuch_thread_get_total_messages(self.thread))
 +}
 +
 +/* Get a notmuch_messages_t iterator for the top-level messages in
 + * 'thread'.
 + *
 + * This iterator will not necessarily iterate over all of the messages
 + * in the thread. It will only iterate over the messages in the thread
 + * which are not replies to other messages in the thread.
 + *
 + * To iterate over all messages in the thread, the caller will need to
 + * iterate over the result of notmuch_message_get_replies for each
 + * top-level message (and do that recursively for the resulting
 + * messages, etc.).
 + */
 +func (self *Thread) GetToplevelMessages() *Messages {
 + if self.thread == nil {
 + return nil
 + }
 + msgs := C.notmuch_thread_get_toplevel_messages(self.thread)
 + if msgs == nil {
 + return nil
 + }
 + return createMessages(msgs, self)
 +}
 +
 +/* Get a notmuch_messages_t iterator for the top-level messages in
 + * 'thread'.
 + *
 + * This iterator will not necessarily iterate over all of the messages
 + * in the thread. It will only iterate over the messages in the thread
 + * which are not replies to other messages in the thread.
 + *
 + * 

Re: [PATCH 0/7] Various fixes for the Go bindings

2012-07-18 Thread Austin Clements
This series looks good to me other than the few things I commented on.
It's nice to see the Go bindings get a bit of love!

Quoth Adrien Bustany on Jul 18 at  9:34 pm:
 The following patches fix some serious memory management issues with
 the Go bindings, and add some missing functions as well.
 
 Adrien Bustany (7):
   go: Use iota in enum bindings
   go: Add missing MESSAGE_FLAG_EXCLUDED in Flag enum
   go: Allow notmuch objects to be garbage collected
   go: Make Destroy functions safe to call several times
   go: Partially bind notmuch_database_upgrade
   go: Bind notmuch_database_find_message_by_filename
   go: Bind notmuch_thread_t functions
 
  bindings/go/src/notmuch/notmuch.go |  471 
 ++--
  1 files changed, 447 insertions(+), 24 deletions(-)
 
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch


Re: [PATCH] emacs: Make moving to the previous message move to the previous boundary

2012-07-18 Thread Mark Walters
On Sat, 14 Jul 2012, Austin Clements amdra...@mit.edu wrote:
 Previously, notmuch-show-previous-message would move to the beginning
 of the message before the message containing point.  This patch makes
 it instead move to the previous message *boundary*.  That is, if point
 isn't already at the beginning of the message, it moves to the
 beginning of the current message.  This is consistent with
 notmuch-show-next-message, which can be thought of as moving to the
 next message boundary.  Several people have expressed a preference for
 this.

I like this change: the current behaviour has mildly confused/annoyed me
in the past.


 This patch accompanies the series in [0] (though they're independent
 and can be applied in either order).  This makes the behavior of 'p'
 and 'P' in show-mode conceptually similar to the new behavior of 'p'
 in search-mode.

 [0] 1342140319-19859-1-git-send-email-amdra...@mit.edu

  emacs/notmuch-show.el |   10 +++---
  1 file changed, 7 insertions(+), 3 deletions(-)

 diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
 index 6335d45..02e319f 100644
 --- a/emacs/notmuch-show.el
 +++ b/emacs/notmuch-show.el
 @@ -1525,9 +1525,11 @@ thread, navigate to the next thread in the parent 
 search buffer.
(goto-char (point-max)
  
  (defun notmuch-show-previous-message ()
 -  Show the previous message.
 +  Show the previous message or the start of the current message.
(interactive)
 -  (notmuch-show-goto-message-previous)
 +  (if (= (point) (notmuch-show-message-top))
 +  (notmuch-show-goto-message-previous)
 +(notmuch-show-move-to-message-top))
(notmuch-show-mark-read)
(notmuch-show-message-adjust))
  
 @@ -1587,7 +1589,9 @@ to show, nil otherwise.
  (defun notmuch-show-previous-open-message ()
Show the previous open message.
(interactive)
 -  (while (and (notmuch-show-goto-message-previous)
 +  (while (and (if (= (point) (notmuch-show-message-top))
 +   (notmuch-show-goto-message-previous)
 + (notmuch-show-move-to-message-top))
 (not (notmuch-show-message-visible-p
(notmuch-show-mark-read)
(notmuch-show-message-adjust))

I would mildly prefer taking the testing for top of message out of the
loop (I know it's one line longer) as

  (if (= (point) (notmuch-show-message-top))
  (notmuch-show-goto-message-previous)
(notmuch-show-move-to-message-top))
  (while (and (not (notmuch-show-message-visible-p))
  (notmuch-show-goto-message-previous)))

but its obviously fine as is.

Best wishes

Mark
___
notmuch mailing list
notmuch@notmuchmail.org
http://notmuchmail.org/mailman/listinfo/notmuch