]> git.notmuchmail.org Git - notmuch/commitdiff
Merge branch 'release'
authorDavid Bremner <david@tethera.net>
Fri, 9 Sep 2016 01:18:37 +0000 (22:18 -0300)
committerDavid Bremner <david@tethera.net>
Fri, 9 Sep 2016 01:18:37 +0000 (22:18 -0300)
hand fixed conflicts with NEWS

185 files changed:
.mailmap [new file with mode: 0644]
COPYING
COPYING-GPL-3
INSTALL
Makefile.local
NEWS
README
README.rst
bindings/go/.gitignore [deleted file]
bindings/go/LICENSE [deleted file]
bindings/go/Makefile [deleted file]
bindings/go/README [deleted file]
bindings/go/src/notmuch-addrlookup/addrlookup.go [deleted file]
bindings/go/src/notmuch/notmuch.go [deleted file]
bindings/python/README
bindings/python/docs/COPYING
bindings/python/docs/source/index.rst
bindings/python/notmuch/__init__.py
bindings/python/notmuch/compat.py
bindings/python/notmuch/database.py
bindings/python/notmuch/directory.py
bindings/python/notmuch/errors.py
bindings/python/notmuch/filenames.py
bindings/python/notmuch/globals.py
bindings/python/notmuch/message.py
bindings/python/notmuch/messages.py
bindings/python/notmuch/query.py
bindings/python/notmuch/tag.py
bindings/python/notmuch/thread.py
bindings/python/notmuch/threads.py
bindings/python/setup.py
bindings/ruby/database.c
bindings/ruby/defs.h
bindings/ruby/directory.c
bindings/ruby/filenames.c
bindings/ruby/init.c
bindings/ruby/message.c
bindings/ruby/messages.c
bindings/ruby/query.c
bindings/ruby/status.c
bindings/ruby/tags.c
bindings/ruby/thread.c
bindings/ruby/threads.c
compat/compat.h
compat/function-attributes.h
completion/README
completion/notmuch-completion.bash
configure
contrib/go/.gitignore [new file with mode: 0644]
contrib/go/LICENSE [new file with mode: 0644]
contrib/go/Makefile [new file with mode: 0644]
contrib/go/README [new file with mode: 0644]
contrib/go/src/notmuch-addrlookup/addrlookup.go [new file with mode: 0644]
contrib/go/src/notmuch/notmuch.go [new file with mode: 0644]
contrib/notmuch-mutt/README
contrib/notmuch-mutt/notmuch-mutt
contrib/notmuch-vim/Makefile [deleted file]
contrib/notmuch-vim/README [deleted file]
contrib/notmuch-vim/notmuch.yaml [deleted file]
contrib/notmuch-vim/plugin/notmuch.vim [deleted file]
contrib/notmuch-vim/syntax/notmuch-compose.vim [deleted file]
contrib/notmuch-vim/syntax/notmuch-folders.vim [deleted file]
contrib/notmuch-vim/syntax/notmuch-git-diff.vim [deleted file]
contrib/notmuch-vim/syntax/notmuch-search.vim [deleted file]
contrib/notmuch-vim/syntax/notmuch-show.vim [deleted file]
crypto.c
debian/control
debugger.c
devel/man-to-mdwn.pl
devel/nmbug/doc/man5/notmuch-report.json.5.rst
devel/nmbug/nmbug
devel/nmbug/notmuch-report
devel/nmbug/notmuch-report.json
devel/try-emacs-mua
doc/doxygen.cfg
doc/index.rst
doc/man1/notmuch-config.rst
doc/man1/notmuch-dump.rst
doc/man1/notmuch-restore.rst
doc/man1/notmuch.rst
doc/man7/notmuch-search-terms.rst
doc/notmuch-emacs.rst
emacs/coolj.el
emacs/make-deps.el
emacs/notmuch-address.el
emacs/notmuch-company.el
emacs/notmuch-crypto.el
emacs/notmuch-hello.el
emacs/notmuch-jump.el
emacs/notmuch-lib.el
emacs/notmuch-maildir-fcc.el
emacs/notmuch-message.el
emacs/notmuch-mua.el
emacs/notmuch-parser.el
emacs/notmuch-print.el
emacs/notmuch-query.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el
emacs/notmuch-version.el.tmpl
emacs/notmuch-wash.el
emacs/notmuch.el
hooks.c
lib/Makefile.local
lib/built-with.c [new file with mode: 0644]
lib/config.cc [new file with mode: 0644]
lib/database-private.h
lib/database.cc
lib/directory.cc
lib/filenames.c
lib/gen-version-script.sh
lib/index.cc
lib/message-file.c
lib/message.cc
lib/messages.c
lib/notmuch-private.h
lib/notmuch.h
lib/parse-time-vrp.cc
lib/parse-time-vrp.h
lib/query-fp.cc [new file with mode: 0644]
lib/query-fp.h [new file with mode: 0644]
lib/query.cc
lib/sha1.c
lib/string-list.c
lib/tags.c
lib/thread.cc
mime-node.c
notmuch-client.h
notmuch-compact.c
notmuch-config.c
notmuch-count.c
notmuch-dump.c
notmuch-emacs-mua
notmuch-insert.c
notmuch-new.c
notmuch-reply.c
notmuch-restore.c
notmuch-search.c
notmuch-setup.c
notmuch-show.c
notmuch-tag.c
notmuch-time.c
notmuch.c
packaging/fedora/notmuch.spec
parse-time-string/parse-time-string.c
parse-time-string/parse-time-string.h
performance-test/Makefile.local
performance-test/README
performance-test/notmuch-memory-test
performance-test/notmuch-time-test
query-string.c
sprinter-sexp.c
test/Makefile.local
test/README
test/T030-config.sh
test/T040-setup.sh
test/T150-tagging.sh
test/T210-raw.sh
test/T240-dump-restore.sh
test/T310-emacs.sh
test/T360-symbol-hiding.sh
test/T395-ruby.sh
test/T500-search-date.sh
test/T590-libconfig.sh [new file with mode: 0755]
test/T600-named-queries.sh [new file with mode: 0755]
test/T620-lock.sh [new file with mode: 0755]
test/database-test.c
test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
test/notmuch-test
test/notmuch-test.h [new file with mode: 0644]
test/parse-time.c
test/random-corpus.c
test/smtp-dummy.c
test/test-databases/Makefile.local
test/test-lib-common.sh
test/test-lib.el
test/test-lib.sh
util/error_util.c
util/error_util.h
util/hex-escape.c
util/string-util.c
util/xutil.c
util/xutil.h
util/zlib-extra.c

diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..935c6eb
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
+Peter Feigl <craven@gmx.net>
+Nate <nstraz@redhat.com>
+Ali Polatel <alip@penguen.ev>
+Stefan <aeuii@posteo.de>
+Patrick Totzke <patricktotzke@googlemail.com>
+Patrick Totzke <patricktotzke@gmail.com>
+Patrick Totzke <p.totzke@ed.ac.uk>
+Mark Walters <markwalters1009@gmail.com>
diff --git a/COPYING b/COPYING
index 6dea6939b3fb9a3fe26966b831ef4f99ae117bba..4e744d2616afd98936e48604a6f87aeb70cbc51a 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -12,4 +12,4 @@ General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program, (in the COPYING-GPL-3 file in this
-directory). If not, see http://www.gnu.org/licenses/ 
+directory). If not, see https://www.gnu.org/licenses/
index 443254047416efc10bc53cb1adb5421680d5a647..4c493545cb232b90198902160c366951f6e95367 100644 (file)
@@ -2,7 +2,7 @@
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007
 
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -646,7 +646,7 @@ the "copyright" line and a pointer to where the full notice is found.
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 Also add information on how to contact you by electronic and paper mail.
 
@@ -665,12 +665,12 @@ might be different; for a GUI interface, you would use an "about box".
   You should also get your employer (if you work as a programmer) or school,
 if any, to sign a "copyright disclaimer" for the program, if necessary.
 For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
+<https://www.gnu.org/licenses/>.
 
   The GNU General Public License does not permit incorporating your program
 into proprietary programs.  If your program is a subroutine library, you
 may consider it more useful to permit linking proprietary applications with
 the library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.  But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+<https://www.gnu.org/philosophy/why-not-lgpl.html>.
 
diff --git a/INSTALL b/INSTALL
index b1b9cd55af4380ab525701d39ed7ece8b5c8ef9a..6099ed01b39d1c4a9b4476bddebe40d0e5f2109b 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -30,7 +30,7 @@ Talloc, and zlib which are each described below:
        It provides all the real machinery of indexing and searching,
        (including the very nice parsing of the query string).
 
-       Xapian is available from http://xapian.org
+       Xapian is available from https://xapian.org
 
        Note: Notmuch will work best with Xapian 1.0.18 (or later) or
        Xapian 1.1.4 (or later). Previous versions of Xapian (whether
@@ -58,7 +58,7 @@ Talloc, and zlib which are each described below:
        made development of Notmuch much easier and much less prone to
        memory leaks.
 
-       Talloc is available from http://talloc.samba.org/
+       Talloc is available from https://talloc.samba.org/
 
        zlib
        ----
index 066ecf23360c469eddb0c16490141c6f99541489..5587cd2c29a635c004df1b1885409905a4b85b60 100644 (file)
@@ -10,7 +10,7 @@
 # repository), we let git append identification of the actual commit.
 PACKAGE=notmuch
 
-IS_GIT=$(shell if [ -d ${srcdir}/.git ] ; then echo yes ; else echo no; fi)
+IS_GIT:=$(if $(wildcard ${srcdir}/.git),yes,no)
 
 ifeq ($(IS_GIT),yes)
 DATE:=$(shell git --git-dir=${srcdir}/.git log --date=short -1 --pretty=format:%cd)
@@ -35,7 +35,7 @@ DEB_TAG=debian/$(UPSTREAM_TAG)-1
 
 RELEASE_HOST=notmuchmail.org
 RELEASE_DIR=/srv/notmuchmail.org/www/releases
-RELEASE_URL=http://notmuchmail.org/releases
+RELEASE_URL=https://notmuchmail.org/releases
 TAR_FILE=$(PACKAGE)-$(VERSION).tar.gz
 DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.gz
 SHA1_FILE=$(TAR_FILE).sha1
@@ -191,7 +191,7 @@ release-message:
        @echo "the Xapian library to provide fast, full-text search with a convenient"
        @echo "search syntax."
        @echo ""
-       @echo "For more about notmuch, see http://notmuchmail.org"
+       @echo "For more about notmuch, see https://notmuchmail.org"
 
 # This is a chain of dependencies rather than a simple list simply to
 # avoid the messages getting interleaved in the case of a parallel
diff --git a/NEWS b/NEWS
index 31d94274da2bfe2fdc06f73ed0b705000941490b..dbb929879988eb041374d8c5c476cdf979637bb1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,35 @@
+Notmuch 0.23 (UNRELEASED)
+=========================
+
+Emacs
+-----
+
+Face customization is easier
+
+  New faces `notmuch-search-flagged-face` and
+  `notmuch-search-unread-face` are used by default by
+  `notmuch-search-line-faces`. Customize `notmuch-faces` to modify
+  them.
+
+Ruby Bindings
+-------------
+
+Add support for `notmuch_database_get_all_tags`
+
+Go Bindings
+-----------
+
+Go bindings moved to contrib
+
+Add support for `notmuch_threads_t` and `notmuch_thread_t`
+
+Fixed constant values so they are not all zero anymore.
+
+  Previously, it was impossible to open writable database handles,
+  because DATABASE_MODE_READ_ONLY and DATABASE_MODE_READ_WRITE were
+  both set to zero.
+  The same issue occured with sort modes.
+
 Notmuch 0.22.2 (2016-09-08)
 ===========================
 
@@ -2399,7 +2431,7 @@ Ruby bindings are now much more complete
 
 Python bindings have been updated and extended
 
-  (docs online at http://packages.python.org/notmuch/)
+  (docs online at https://notmuch.readthedocs.io/)
 
   New bindings:
 
diff --git a/README b/README
index d92fcfdfc4b0ad306cce3cb5510ae95d13ddfcef..0aa9a080d075a350b6d3610ddea94fda3119a49f 100644 (file)
--- a/README
+++ b/README
@@ -58,7 +58,7 @@ Contacting users and developers
 -------------------------------
 The website for Notmuch is:
 
-       http://notmuchmail.org
+       https://notmuchmail.org
 
 The mailing list address for the notmuch community is:
 
index 7417ddcb439d304d14d33636456cc5cac0696a84..7ff3198cbc8020ba75959e9b1a302c4a79f4318c 100644 (file)
@@ -1,11 +1,11 @@
-If you're reading this on http://github.com/notmuch/notmuch, this is a
+If you're reading this on https://github.com/notmuch/notmuch, this is a
 read-only mirror of the notmuch project.
 
-For more information about the project, see http://notmuchmail.org.
+For more information about the project, see https://notmuchmail.org.
 
 Please don't send us pull requests on github. If you have a feature
 branch that you want us to look at, use ``git send-email`` to send it
 to notmuch@notmuchmail.org.
 
 For more information about contributing to the project, see
-http://notmuchmail.org/contributing/.
+https://notmuchmail.org/contributing/.
diff --git a/bindings/go/.gitignore b/bindings/go/.gitignore
deleted file mode 100644 (file)
index c394479..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-src/github.com/
-pkg/
-bin/
diff --git a/bindings/go/LICENSE b/bindings/go/LICENSE
deleted file mode 100644 (file)
index 4362b49..0000000
+++ /dev/null
@@ -1,502 +0,0 @@
-                  GNU LESSER GENERAL PUBLIC LICENSE
-                       Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
- 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL.  It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
-  This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it.  You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
-  When we speak of free software, we are referring to freedom of use,
-not price.  Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
-  To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights.  These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
-
-  We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
-  To protect each distributor, we want to make it very clear that
-there is no warranty for the free library.  Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-\f
-  Finally, software patents pose a constant threat to the existence of
-any free program.  We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder.  Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
-  Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License.  This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License.  We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
-  When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library.  The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom.  The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
-  We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License.  It also provides other free software developers Less
-of an advantage over competing non-free programs.  These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries.  However, the Lesser license provides advantages in certain
-special circumstances.
-
-  For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard.  To achieve this, non-free programs must be
-allowed to use the library.  A more frequent case is that a free
-library does the same job as widely used non-free libraries.  In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
-  In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software.  For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
-  Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-\f
-                  GNU LESSER GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
-
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
-  1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
-  You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-\f
-  2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
-
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-\f
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
-  If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-\f
-  6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Use a suitable shared library mechanism for linking with the
-    Library.  A suitable mechanism is one that (1) uses at run time a
-    copy of the library already present on the user's computer system,
-    rather than copying library functions into the executable, and (2)
-    will operate properly with a modified version of the library, if
-    the user installs one, as long as the modified version is
-    interface-compatible with the version that the work was made with.
-
-    c) Accompany the work with a written offer, valid for at
-    least three years, to give the same user the materials
-    specified in Subsection 6a, above, for a charge no more
-    than the cost of performing this distribution.
-
-    d) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    e) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-\f
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License.  However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
-  9. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Library or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-\f
-  11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded.  In such case, this License incorporates the limitation as if
-written in the body of this License.
-
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation.  If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-\f
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission.  For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this.  Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
-                            NO WARRANTY
-
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-\f
-           How to Apply These Terms to Your New Libraries
-
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
-  To apply these terms, attach the following notices to the library.  It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the library's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public
-    License along with this library; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
-  <signature of Ty Coon>, 1 April 1990
-  Ty Coon, President of Vice
-
-That's all there is to it!
diff --git a/bindings/go/Makefile b/bindings/go/Makefile
deleted file mode 100644 (file)
index 1b9e750..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-# Makefile for the go bindings of notmuch
-
-export GOPATH      ?= $(shell pwd)
-export CGO_CFLAGS  ?= -I../../../../lib
-export CGO_LDFLAGS ?= -L../../../../lib
-
-GO         ?= go
-GOFMT      ?= gofmt
-
-all: notmuch notmuch-addrlookup
-
-.PHONY: notmuch
-notmuch:
-       $(GO) install notmuch
-
-.PHONY: goconfig
-goconfig:
-       if [ ! -d github.com/msbranco/goconfig ]; then \
-           $(GO) get github.com/msbranco/goconfig; \
-       fi
-
-.PHONY: notmuch-addrlookup
-notmuch-addrlookup: notmuch goconfig
-       $(GO) install notmuch-addrlookup
-
-.PHONY: format
-format:
-       $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch
-       $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch-addrlookup
-
-.PHONY: check-format
-check-format:
-       $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch
-       $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch-addrlookup
-
-.PHONY: clean
-clean:
-       $(GO) clean notmuch
-       $(GO) clean notmuch-addrlookup
-       rm -rf pkg bin
diff --git a/bindings/go/README b/bindings/go/README
deleted file mode 100644 (file)
index 18a54b6..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-go-notmuch
-==========
-
-simple go bindings to the libnotmuch library[1].
-
-they are heavily inspired from the vala bindings from Sebastian Spaeth: 
-  https://github.com/spaetz/vala-notmuch
-
-note: the whole library hasn't been wrapped (yet?)
-
-todo
-----
-
- - improve notmuch-addrlookup regexp
-
-[1] http://notmuchmail.org/
\ No newline at end of file
diff --git a/bindings/go/src/notmuch-addrlookup/addrlookup.go b/bindings/go/src/notmuch-addrlookup/addrlookup.go
deleted file mode 100644 (file)
index 916e5bb..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-package main
-
-// stdlib imports
-import "os"
-import "path"
-import "log"
-import "fmt"
-import "regexp"
-import "strings"
-import "sort"
-
-// 3rd-party imports
-import "notmuch"
-import "github.com/msbranco/goconfig"
-
-type mail_addr_freq struct {
-       addr  string
-       count [3]uint
-}
-
-type frequencies map[string]uint
-
-/* Used to sort the email addresses from most to least used */
-func sort_by_freq(m1, m2 *mail_addr_freq) int {
-       if m1.count[0] == m2.count[0] &&
-               m1.count[1] == m2.count[1] &&
-               m1.count[2] == m2.count[2] {
-               return 0
-       }
-
-       if m1.count[0] > m2.count[0] ||
-               m1.count[0] == m2.count[0] &&
-                       m1.count[1] > m2.count[1] ||
-               m1.count[0] == m2.count[0] &&
-                       m1.count[1] == m2.count[1] &&
-                       m1.count[2] > m2.count[2] {
-               return -1
-       }
-
-       return 1
-}
-
-type maddresses []*mail_addr_freq
-
-func (self *maddresses) Len() int {
-       return len(*self)
-}
-
-func (self *maddresses) Less(i, j int) bool {
-       m1 := (*self)[i]
-       m2 := (*self)[j]
-       v := sort_by_freq(m1, m2)
-       if v <= 0 {
-               return true
-       }
-       return false
-}
-
-func (self *maddresses) Swap(i, j int) {
-       (*self)[i], (*self)[j] = (*self)[j], (*self)[i]
-}
-
-// find most frequent real name for each mail address
-func frequent_fullname(freqs frequencies) string {
-       var maxfreq uint = 0
-       fullname := ""
-       freqs_sz := len(freqs)
-
-       for mail, freq := range freqs {
-               if (freq > maxfreq && mail != "") || freqs_sz == 1 {
-                       // only use the entry if it has a real name
-                       // or if this is the only entry
-                       maxfreq = freq
-                       fullname = mail
-               }
-       }
-       return fullname
-}
-
-func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies {
-
-       freqs := make(frequencies)
-
-       pattern := `\s*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
-       // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
-       //      "<?(?P<mail>\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)"
-       pattern = `.*` + strings.ToLower(name) + `.*`
-       var re *regexp.Regexp = nil
-       var err error = nil
-       if re, err = regexp.Compile(pattern); err != nil {
-               log.Printf("error: %v\n", err)
-               return &freqs
-       }
-
-       headers := []string{"from"}
-       if pass == 1 {
-               headers = append(headers, "to", "cc", "bcc")
-       }
-
-       for ; msgs.Valid(); msgs.MoveToNext() {
-               msg := msgs.Get()
-               //println("==> msg [", msg.GetMessageId(), "]")
-               for _, header := range headers {
-                       froms := strings.ToLower(msg.GetHeader(header))
-                       //println("  froms: ["+froms+"]")
-                       for _, from := range strings.Split(froms, ",") {
-                               from = strings.Trim(from, " ")
-                               match := re.FindString(from)
-                               //println("  -> match: ["+match+"]")
-                               occ, ok := freqs[match]
-                               if !ok {
-                                       freqs[match] = 0
-                                       occ = 0
-                               }
-                               freqs[match] = occ + 1
-                       }
-               }
-       }
-       return &freqs
-}
-
-func search_address_passes(queries [3]*notmuch.Query, name string) []string {
-       var val []string
-       addr_freq := make(map[string]*mail_addr_freq)
-       addr_to_realname := make(map[string]*frequencies)
-
-       var pass uint = 0 // 0-based
-       for _, query := range queries {
-               if query == nil {
-                       //println("**warning: idx [",idx,"] contains a nil query")
-                       continue
-               }
-               msgs := query.SearchMessages()
-               ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname)
-               for addr, count := range *ht {
-                       freq, ok := addr_freq[addr]
-                       if !ok {
-                               freq = &mail_addr_freq{addr: addr, count: [3]uint{0, 0, 0}}
-                       }
-                       freq.count[pass] = count
-                       addr_freq[addr] = freq
-               }
-               msgs.Destroy()
-               pass += 1
-       }
-
-       addrs := make(maddresses, len(addr_freq))
-       {
-               iaddr := 0
-               for _, freq := range addr_freq {
-                       addrs[iaddr] = freq
-                       iaddr += 1
-               }
-       }
-       sort.Sort(&addrs)
-
-       for _, addr := range addrs {
-               freqs, ok := addr_to_realname[addr.addr]
-               if ok {
-                       val = append(val, frequent_fullname(*freqs))
-               } else {
-                       val = append(val, addr.addr)
-               }
-       }
-       //println("val:",val)
-       return val
-}
-
-type address_matcher struct {
-       // the notmuch database
-       db *notmuch.Database
-       // full path of the notmuch database
-       user_db_path string
-       // user primary email
-       user_primary_email string
-       // user tag to mark from addresses as in the address book
-       user_addrbook_tag string
-}
-
-func new_address_matcher() *address_matcher {
-       // honor NOTMUCH_CONFIG
-       home := os.Getenv("NOTMUCH_CONFIG")
-       if home == "" {
-               home = os.Getenv("HOME")
-       }
-
-       cfg, err := goconfig.ReadConfigFile(path.Join(home, ".notmuch-config"))
-       if err != nil {
-               log.Fatalf("error loading config file:", err)
-       }
-
-       db_path, _ := cfg.GetString("database", "path")
-       primary_email, _ := cfg.GetString("user", "primary_email")
-       addrbook_tag, err := cfg.GetString("user", "addrbook_tag")
-       if err != nil {
-               addrbook_tag = "addressbook"
-       }
-
-       self := &address_matcher{db: nil,
-               user_db_path:       db_path,
-               user_primary_email: primary_email,
-               user_addrbook_tag:  addrbook_tag}
-       return self
-}
-
-func (self *address_matcher) run(name string) {
-       queries := [3]*notmuch.Query{}
-
-       // open the database
-       if db, status := notmuch.OpenDatabase(self.user_db_path,
-               notmuch.DATABASE_MODE_READ_ONLY); status == notmuch.STATUS_SUCCESS {
-               self.db = db
-       } else {
-               log.Fatalf("Failed to open the database: %v\n", status)
-       }
-
-       // pass 1: look at all from: addresses with the address book tag
-       query := "tag:" + self.user_addrbook_tag
-       if name != "" {
-               query = query + " and from:" + name + "*"
-       }
-       queries[0] = self.db.CreateQuery(query)
-
-       // pass 2: look at all to: addresses sent from our primary mail
-       query = ""
-       if name != "" {
-               query = "to:" + name + "*"
-       }
-       if self.user_primary_email != "" {
-               query = query + " from:" + self.user_primary_email
-       }
-       queries[1] = self.db.CreateQuery(query)
-
-       // if that leads only to a few hits, we check every from too
-       if queries[0].CountMessages()+queries[1].CountMessages() < 10 {
-               query = ""
-               if name != "" {
-                       query = "from:" + name + "*"
-               }
-               queries[2] = self.db.CreateQuery(query)
-       }
-
-       // actually retrieve and sort addresses
-       results := search_address_passes(queries, name)
-       for _, v := range results {
-               if v != "" && v != "\n" {
-                       fmt.Println(v)
-               }
-       }
-       return
-}
-
-func main() {
-       //fmt.Println("args:",os.Args)
-       app := new_address_matcher()
-       name := ""
-       if len(os.Args) > 1 {
-               name = os.Args[1]
-       }
-       app.run(name)
-}
diff --git a/bindings/go/src/notmuch/notmuch.go b/bindings/go/src/notmuch/notmuch.go
deleted file mode 100644 (file)
index 0fff1ab..0000000
+++ /dev/null
@@ -1,1146 +0,0 @@
-// Wrapper around the notmuch library
-
-package notmuch
-
-/*
-#cgo LDFLAGS: -lnotmuch
-
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include "notmuch.h"
-*/
-import "C"
-import "unsafe"
-
-// Status codes used for the return values of most functions
-type Status C.notmuch_status_t
-
-const (
-       STATUS_SUCCESS Status = iota
-       STATUS_OUT_OF_MEMORY
-       STATUS_READ_ONLY_DATABASE
-       STATUS_XAPIAN_EXCEPTION
-       STATUS_FILE_ERROR
-       STATUS_FILE_NOT_EMAIL
-       STATUS_DUPLICATE_MESSAGE_ID
-       STATUS_NULL_POINTER
-       STATUS_TAG_TOO_LONG
-       STATUS_UNBALANCED_FREEZE_THAW
-       STATUS_UNBALANCED_ATOMIC
-
-       STATUS_LAST_STATUS
-)
-
-func (self Status) String() string {
-       var p *C.char
-
-       // p is read-only
-       p = C.notmuch_status_to_string(C.notmuch_status_t(self))
-       if p != nil {
-               s := C.GoString(p)
-               return s
-       }
-       return ""
-}
-
-/* Various opaque data types. For each notmuch_<foo>_t see the various
- * notmuch_<foo> functions below. */
-
-type Database struct {
-       db *C.notmuch_database_t
-}
-
-type Query struct {
-       query *C.notmuch_query_t
-}
-
-type Threads struct {
-       threads *C.notmuch_threads_t
-}
-
-type Thread struct {
-       thread *C.notmuch_thread_t
-}
-
-type Messages struct {
-       messages *C.notmuch_messages_t
-}
-
-type Message struct {
-       message *C.notmuch_message_t
-}
-
-type Tags struct {
-       tags *C.notmuch_tags_t
-}
-
-type Directory struct {
-       dir *C.notmuch_directory_t
-}
-
-type Filenames struct {
-       fnames *C.notmuch_filenames_t
-}
-
-type DatabaseMode C.notmuch_database_mode_t
-
-const (
-       DATABASE_MODE_READ_ONLY DatabaseMode = 0
-       DATABASE_MODE_READ_WRITE
-)
-
-// Create a new, empty notmuch database located at 'path'
-func NewDatabase(path string) (*Database, Status) {
-
-       var c_path *C.char = C.CString(path)
-       defer C.free(unsafe.Pointer(c_path))
-
-       if c_path == nil {
-               return nil, STATUS_OUT_OF_MEMORY
-       }
-
-       self := &Database{db: nil}
-       st := Status(C.notmuch_database_create(c_path, &self.db))
-       if st != STATUS_SUCCESS {
-               return nil, st
-       }
-       return self, st
-}
-
-/* Open an existing notmuch database located at 'path'.
- *
- * The database should have been created at some time in the past,
- * (not necessarily by this process), by calling
- * notmuch_database_create with 'path'. By default the database should be
- * opened for reading only. In order to write to the database you need to
- * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode.
- *
- * An existing notmuch database can be identified by the presence of a
- * directory named ".notmuch" below 'path'.
- *
- * The caller should call notmuch_database_destroy when finished with
- * this database.
- *
- * In case of any failure, this function returns NULL, (after printing
- * an error message on stderr).
- */
-func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) {
-
-       var c_path *C.char = C.CString(path)
-       defer C.free(unsafe.Pointer(c_path))
-
-       if c_path == nil {
-               return nil, STATUS_OUT_OF_MEMORY
-       }
-
-       self := &Database{db: nil}
-       st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db))
-       if st != STATUS_SUCCESS {
-               return nil, st
-       }
-       return self, st
-}
-
-/* Close the given notmuch database, freeing all associated
- * resources. See notmuch_database_open. */
-func (self *Database) Close() Status {
-       return Status(C.notmuch_database_destroy(self.db))
-}
-
-/* Return the database path of the given database.
- */
-func (self *Database) GetPath() string {
-
-       /* The return value is a string owned by notmuch so should not be
-        * modified nor freed by the caller. */
-       var p *C.char = C.notmuch_database_get_path(self.db)
-       if p != nil {
-               s := C.GoString(p)
-               return s
-       }
-       return ""
-}
-
-/* Return the database format version of the given database. */
-func (self *Database) GetVersion() uint {
-       return uint(C.notmuch_database_get_version(self.db))
-}
-
-/* Does this database need to be upgraded before writing to it?
- *
- * If this function returns TRUE then no functions that modify the
- * database (notmuch_database_add_message, notmuch_message_add_tag,
- * notmuch_directory_set_mtime, etc.) will work unless the function
- * notmuch_database_upgrade is called successfully first. */
-func (self *Database) NeedsUpgrade() bool {
-       do_upgrade := C.notmuch_database_needs_upgrade(self.db)
-       if do_upgrade == 0 {
-               return false
-       }
-       return true
-}
-
-// TODO: notmuch_database_upgrade
-
-/* Retrieve a directory object from the database for 'path'.
- *
- * Here, 'path' should be a path relative to the path of 'database'
- * (see notmuch_database_get_path), or else should be an absolute path
- * with initial components that match the path of 'database'.
- *
- * Can return NULL if a Xapian exception occurs.
- */
-func (self *Database) GetDirectory(path string) (*Directory, Status) {
-       var c_path *C.char = C.CString(path)
-       defer C.free(unsafe.Pointer(c_path))
-
-       if c_path == nil {
-               return nil, STATUS_OUT_OF_MEMORY
-       }
-
-       var c_dir *C.notmuch_directory_t
-       st := Status(C.notmuch_database_get_directory(self.db, c_path, &c_dir))
-       if st != STATUS_SUCCESS || c_dir == nil {
-               return nil, st
-       }
-       return &Directory{dir: c_dir}, st
-}
-
-/* Add a new message to the given notmuch database.
- *
- * Here,'filename' should be a path relative to the path of
- * 'database' (see notmuch_database_get_path), or else should be an
- * absolute filename with initial components that match the path of
- * 'database'.
- *
- * The file should be a single mail message (not a multi-message mbox)
- * that is expected to remain at its current location, (since the
- * notmuch database will reference the filename, and will not copy the
- * entire contents of the file.
- *
- * If 'message' is not NULL, then, on successful return '*message'
- * will be initialized to a message object that can be used for things
- * such as adding tags to the just-added message. The user should call
- * notmuch_message_destroy when done with the message. On any failure
- * '*message' will be set to NULL.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
- *
- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
- *     message not added.
- *
- * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
- *     ID as another message already in the database. The new
- *     filename was successfully added to the message in the database
- *     (if not already present).
- *
- * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the
- *     file, (such as permission denied, or file not found,
- *     etc.). Nothing added to the database.
- *
- * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
- *     like an email message. Nothing added to the database.
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so no message can be added.
- */
-func (self *Database) AddMessage(fname string) (*Message, Status) {
-       var c_fname *C.char = C.CString(fname)
-       defer C.free(unsafe.Pointer(c_fname))
-
-       if c_fname == nil {
-               return nil, STATUS_OUT_OF_MEMORY
-       }
-
-       var c_msg *C.notmuch_message_t = new(C.notmuch_message_t)
-       st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg))
-
-       return &Message{message: c_msg}, st
-}
-
-/* Remove a message from the given notmuch database.
- *
- * Note that only this particular filename association is removed from
- * the database. If the same message (as determined by the message ID)
- * is still available via other filenames, then the message will
- * persist in the database for those filenames. When the last filename
- * is removed for a particular message, the database content for that
- * message will be entirely removed.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the
- *     message was removed from the database.
- *
- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
- *     message not removed.
- *
- * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but
- *     the message persists in the database with at least one other
- *     filename.
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so no message can be removed.
- */
-func (self *Database) RemoveMessage(fname string) Status {
-
-       var c_fname *C.char = C.CString(fname)
-       defer C.free(unsafe.Pointer(c_fname))
-
-       if c_fname == nil {
-               return STATUS_OUT_OF_MEMORY
-       }
-
-       st := C.notmuch_database_remove_message(self.db, c_fname)
-       return Status(st)
-}
-
-/* Find a message with the given message_id.
- *
- * If the database contains a message with the given message_id, then
- * a new notmuch_message_t object is returned. The caller should call
- * notmuch_message_destroy when done with the message.
- *
- * This function returns NULL in the following situations:
- *
- *     * No message is found with the given message_id
- *     * An out-of-memory situation occurs
- *     * A Xapian exception occurs
- */
-func (self *Database) FindMessage(message_id string) (*Message, Status) {
-
-       var c_msg_id *C.char = C.CString(message_id)
-       defer C.free(unsafe.Pointer(c_msg_id))
-
-       if c_msg_id == nil {
-               return nil, STATUS_OUT_OF_MEMORY
-       }
-
-       msg := &Message{message: nil}
-       st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message))
-       if st != STATUS_SUCCESS {
-               return nil, st
-       }
-       return msg, st
-}
-
-/* Return a list of all tags found in the database.
- *
- * This function creates a list of all tags found in the database. The
- * resulting list contains all tags from all messages found in the database.
- *
- * On error this function returns NULL.
- */
-func (self *Database) GetAllTags() *Tags {
-       tags := C.notmuch_database_get_all_tags(self.db)
-       if tags == nil {
-               return nil
-       }
-       return &Tags{tags: tags}
-}
-
-/* Create a new query for 'database'.
- *
- * Here, 'database' should be an open database, (see
- * notmuch_database_open and notmuch_database_create).
- *
- * For the query string, we'll document the syntax here more
- * completely in the future, but it's likely to be a specialized
- * version of the general Xapian query syntax:
- *
- * http://xapian.org/docs/queryparser.html
- *
- * As a special case, passing either a length-zero string, (that is ""),
- * or a string consisting of a single asterisk (that is "*"), will
- * result in a query that returns all messages in the database.
- *
- * See notmuch_query_set_sort for controlling the order of results.
- * See notmuch_query_search_messages and notmuch_query_search_threads
- * to actually execute the query.
- *
- * User should call notmuch_query_destroy when finished with this
- * query.
- *
- * Will return NULL if insufficient memory is available.
- */
-func (self *Database) CreateQuery(query string) *Query {
-
-       var c_query *C.char = C.CString(query)
-       defer C.free(unsafe.Pointer(c_query))
-
-       if c_query == nil {
-               return nil
-       }
-
-       q := C.notmuch_query_create(self.db, c_query)
-       if q == nil {
-               return nil
-       }
-       return &Query{query: q}
-}
-
-/* Sort values for notmuch_query_set_sort */
-type Sort C.notmuch_sort_t
-
-const (
-       SORT_OLDEST_FIRST Sort = 0
-       SORT_NEWEST_FIRST
-       SORT_MESSAGE_ID
-       SORT_UNSORTED
-)
-
-/* Return the query_string of this query. See notmuch_query_create. */
-func (self *Query) String() string {
-       // FIXME: do we own 'q' or not ?
-       q := C.notmuch_query_get_query_string(self.query)
-       //defer C.free(unsafe.Pointer(q))
-
-       if q != nil {
-               s := C.GoString(q)
-               return s
-       }
-
-       return ""
-}
-
-/* Specify the sorting desired for this query. */
-func (self *Query) SetSort(sort Sort) {
-       C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort))
-}
-
-/* Return the sort specified for this query. See notmuch_query_set_sort. */
-func (self *Query) GetSort() Sort {
-       return Sort(C.notmuch_query_get_sort(self.query))
-}
-
-/* Execute a query for threads, returning a notmuch_threads_t object
- * which can be used to iterate over the results. The returned threads
- * object is owned by the query and as such, will only be valid until
- * notmuch_query_destroy.
- *
- * Typical usage might be:
- *
- *     notmuch_query_t *query;
- *     notmuch_threads_t *threads;
- *     notmuch_thread_t *thread;
- *
- *     query = notmuch_query_create (database, query_string);
- *
- *     for (threads = notmuch_query_search_threads (query);
- *          notmuch_threads_valid (threads);
- *          notmuch_threads_move_to_next (threads))
- *     {
- *         thread = notmuch_threads_get (threads);
- *         ....
- *         notmuch_thread_destroy (thread);
- *     }
- *
- *     notmuch_query_destroy (query);
- *
- * Note: If you are finished with a thread before its containing
- * query, you can call notmuch_thread_destroy to clean up some memory
- * sooner (as in the above example). Otherwise, if your thread objects
- * are long-lived, then you don't need to call notmuch_thread_destroy
- * and all the memory will still be reclaimed when the query is
- * destroyed.
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_threads_t object. (For consistency, we do provide a
- * notmuch_threads_destroy function, but there's no good reason
- * to call it if the query is about to be destroyed).
- *
- * If a Xapian exception occurs this function will return NULL.
- */
-func (self *Query) SearchThreads() *Threads {
-       threads := C.notmuch_query_search_threads(self.query)
-       if threads == nil {
-               return nil
-       }
-       return &Threads{threads: threads}
-}
-
-/* Execute a query for messages, returning a notmuch_messages_t object
- * which can be used to iterate over the results. The returned
- * messages object is owned by the query and as such, will only be
- * valid until notmuch_query_destroy.
- *
- * Typical usage might be:
- *
- *     notmuch_query_t *query;
- *     notmuch_messages_t *messages;
- *     notmuch_message_t *message;
- *
- *     query = notmuch_query_create (database, query_string);
- *
- *     for (messages = notmuch_query_search_messages (query);
- *          notmuch_messages_valid (messages);
- *          notmuch_messages_move_to_next (messages))
- *     {
- *         message = notmuch_messages_get (messages);
- *         ....
- *         notmuch_message_destroy (message);
- *     }
- *
- *     notmuch_query_destroy (query);
- *
- * Note: If you are finished with a message before its containing
- * query, you can call notmuch_message_destroy to clean up some memory
- * sooner (as in the above example). Otherwise, if your message
- * objects are long-lived, then you don't need to call
- * notmuch_message_destroy and all the memory will still be reclaimed
- * when the query is destroyed.
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_messages_t object. (For consistency, we do provide a
- * notmuch_messages_destroy function, but there's no good
- * reason to call it if the query is about to be destroyed).
- *
- * If a Xapian exception occurs this function will return NULL.
- */
-func (self *Query) SearchMessages() *Messages {
-       msgs := C.notmuch_query_search_messages(self.query)
-       if msgs == nil {
-               return nil
-       }
-       return &Messages{messages: msgs}
-}
-
-/* Destroy a notmuch_query_t along with any associated resources.
- *
- * This will in turn destroy any notmuch_threads_t and
- * notmuch_messages_t objects generated by this query, (and in
- * turn any notmuch_thread_t and notmuch_message_t objects generated
- * from those results, etc.), if such objects haven't already been
- * destroyed.
- */
-func (self *Query) Destroy() {
-       if self.query != nil {
-               C.notmuch_query_destroy(self.query)
-       }
-}
-
-/* Return an estimate of the number of messages matching a search
- *
- * This function performs a search and returns Xapian's best
- * guess as to number of matching messages.
- *
- * If a Xapian exception occurs, this function may return 0 (after
- * printing a message).
- */
-func (self *Query) CountMessages() uint {
-       return uint(C.notmuch_query_count_messages(self.query))
-}
-
-// TODO: wrap threads and thread
-
-/* Is the given 'threads' iterator pointing at a valid thread.
- *
- * When this function returns TRUE, notmuch_threads_get will return a
- * valid object. Whereas when this function returns FALSE,
- * 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) Valid() bool {
-       if self.threads == nil {
-               return false
-       }
-       valid := C.notmuch_threads_valid(self.threads)
-       if valid == 0 {
-               return false
-       }
-       return true
-}
-
-/* Destroy a notmuch_threads_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_threads_t object will be reclaimed when the
- * containg query object is destroyed.
- */
-func (self *Threads) Destroy() {
-       if self.threads != nil {
-               C.notmuch_threads_destroy(self.threads)
-       }
-}
-
-/* Is the given 'messages' iterator pointing at a valid message.
- *
- * When this function returns TRUE, notmuch_messages_get will return a
- * valid object. Whereas when this function returns FALSE,
- * notmuch_messages_get will return NULL.
- *
- * See the documentation of notmuch_query_search_messages for example
- * code showing how to iterate over a notmuch_messages_t object.
- */
-func (self *Messages) Valid() bool {
-       if self.messages == nil {
-               return false
-       }
-       valid := C.notmuch_messages_valid(self.messages)
-       if valid == 0 {
-               return false
-       }
-       return true
-}
-
-/* Get the current message from 'messages' as a notmuch_message_t.
- *
- * Note: The returned message belongs to 'messages' and has a lifetime
- * identical to it (and the query to which it belongs).
- *
- * See the documentation of notmuch_query_search_messages for example
- * code showing how to iterate over a notmuch_messages_t object.
- *
- * If an out-of-memory situation occurs, this function will return
- * NULL.
- */
-func (self *Messages) Get() *Message {
-       if self.messages == nil {
-               return nil
-       }
-       msg := C.notmuch_messages_get(self.messages)
-       if msg == nil {
-               return nil
-       }
-       return &Message{message: msg}
-}
-
-/* Move the 'messages' iterator to the next message.
- *
- * If 'messages' is already pointing at the last message then the
- * iterator will be moved to a point just beyond that last message,
- * (where notmuch_messages_valid will return FALSE and
- * notmuch_messages_get will return NULL).
- *
- * See the documentation of notmuch_query_search_messages for example
- * code showing how to iterate over a notmuch_messages_t object.
- */
-func (self *Messages) MoveToNext() {
-       if self.messages == nil {
-               return
-       }
-       C.notmuch_messages_move_to_next(self.messages)
-}
-
-/* Destroy a notmuch_messages_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_messages_t object will be reclaimed when the containing
- * query object is destroyed.
- */
-func (self *Messages) Destroy() {
-       if self.messages != nil {
-               C.notmuch_messages_destroy(self.messages)
-       }
-}
-
-/* Return a list of tags from all messages.
- *
- * The resulting list is guaranteed not to contain duplicated tags.
- *
- * WARNING: You can no longer iterate over messages after calling this
- * function, because the iterator will point at the end of the list.
- * We do not have a function to reset the iterator yet and the only
- * way how you can iterate over the list again is to recreate the
- * message list.
- *
- * The function returns NULL on error.
- */
-func (self *Messages) CollectTags() *Tags {
-       if self.messages == nil {
-               return nil
-       }
-       tags := C.notmuch_messages_collect_tags(self.messages)
-       if tags == nil {
-               return nil
-       }
-       return &Tags{tags: tags}
-}
-
-/* Get the message ID of 'message'.
- *
- * The returned string belongs to 'message' and as such, should not be
- * modified by the caller and will only be valid for as long as the
- * message is valid, (which is until the query from which it derived
- * is destroyed).
- *
- * This function will not return NULL since Notmuch ensures that every
- * message has a unique message ID, (Notmuch will generate an ID for a
- * message if the original file does not contain one).
- */
-func (self *Message) GetMessageId() string {
-
-       if self.message == nil {
-               return ""
-       }
-       id := C.notmuch_message_get_message_id(self.message)
-       // we dont own id
-       // defer C.free(unsafe.Pointer(id))
-       if id == nil {
-               return ""
-       }
-       return C.GoString(id)
-}
-
-/* Get the thread ID of 'message'.
- *
- * The returned string belongs to 'message' and as such, should not be
- * modified by the caller and will only be valid for as long as the
- * message is valid, (for example, until the user calls
- * notmuch_message_destroy on 'message' or until a query from which it
- * derived is destroyed).
- *
- * This function will not return NULL since Notmuch ensures that every
- * message belongs to a single thread.
- */
-func (self *Message) GetThreadId() string {
-
-       if self.message == nil {
-               return ""
-       }
-       id := C.notmuch_message_get_thread_id(self.message)
-       // we dont own id
-       // defer C.free(unsafe.Pointer(id))
-
-       if id == nil {
-               return ""
-       }
-
-       return C.GoString(id)
-}
-
-/* Get a notmuch_messages_t iterator for all of the replies to
- * 'message'.
- *
- * Note: This call only makes sense if 'message' was ultimately
- * obtained from a notmuch_thread_t object, (such as by coming
- * directly from the result of calling notmuch_thread_get_
- * toplevel_messages or by any number of subsequent
- * calls to notmuch_message_get_replies).
- *
- * If 'message' was obtained through some non-thread means, (such as
- * by a call to notmuch_query_search_messages), then this function
- * will return NULL.
- *
- * If there are no replies to 'message', this function will return
- * NULL. (Note that notmuch_messages_valid will accept that NULL
- * value as legitimate, and simply return FALSE for it.)
- */
-func (self *Message) GetReplies() *Messages {
-       if self.message == nil {
-               return nil
-       }
-       msgs := C.notmuch_message_get_replies(self.message)
-       if msgs == nil {
-               return nil
-       }
-       return &Messages{messages: msgs}
-}
-
-/* Get a filename for the email corresponding to 'message'.
- *
- * The returned filename is an absolute filename, (the initial
- * component will match notmuch_database_get_path() ).
- *
- * The returned string belongs to the message so should not be
- * modified or freed by the caller (nor should it be referenced after
- * the message is destroyed).
- *
- * Note: If this message corresponds to multiple files in the mail
- * store, (that is, multiple files contain identical message IDs),
- * this function will arbitrarily return a single one of those
- * filenames.
- */
-func (self *Message) GetFileName() string {
-       if self.message == nil {
-               return ""
-       }
-       fname := C.notmuch_message_get_filename(self.message)
-       // we dont own fname
-       // defer C.free(unsafe.Pointer(fname))
-
-       if fname == nil {
-               return ""
-       }
-
-       return C.GoString(fname)
-}
-
-type Flag C.notmuch_message_flag_t
-
-const (
-       MESSAGE_FLAG_MATCH Flag = 0
-)
-
-/* Get a value of a flag for the email corresponding to 'message'. */
-func (self *Message) GetFlag(flag Flag) bool {
-       if self.message == nil {
-               return false
-       }
-       v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag))
-       if v == 0 {
-               return false
-       }
-       return true
-}
-
-/* Set a value of a flag for the email corresponding to 'message'. */
-func (self *Message) SetFlag(flag Flag, value bool) {
-       if self.message == nil {
-               return
-       }
-       var v C.notmuch_bool_t = 0
-       if value {
-               v = 1
-       }
-       C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v)
-}
-
-/* Get the timestamp (seconds since the epoch) of 'message'.
- *
- * Return status:
- *
- * NOTMUCH_STATUS_SUCCESS: Timestamp successfully retrieved
- *
- * NOTMUCH_STATUS_NULL_POINTER: The 'message' argument is NULL
- *
- */
-func (self *Message) GetDate() (int64, Status) {
-       if self.message == nil {
-               return -1, STATUS_NULL_POINTER
-       }
-       timestamp := C.notmuch_message_get_date(self.message)
-       return int64(timestamp), STATUS_SUCCESS
-}
-
-/* Get the value of the specified header from 'message'.
- *
- * The value will be read from the actual message file, not from the
- * notmuch database. The header name is case insensitive.
- *
- * The returned string belongs to the message so should not be
- * modified or freed by the caller (nor should it be referenced after
- * the message is destroyed).
- *
- * Returns an empty string ("") if the message does not contain a
- * header line matching 'header'. Returns NULL if any error occurs.
- */
-func (self *Message) GetHeader(header string) string {
-       if self.message == nil {
-               return ""
-       }
-
-       var c_header *C.char = C.CString(header)
-       defer C.free(unsafe.Pointer(c_header))
-
-       /* we dont own value */
-       value := C.notmuch_message_get_header(self.message, c_header)
-       if value == nil {
-               return ""
-       }
-
-       return C.GoString(value)
-}
-
-/* Get the tags for 'message', returning a notmuch_tags_t object which
- * can be used to iterate over all tags.
- *
- * The tags object is owned by the message and as such, will only be
- * valid for as long as the message is valid, (which is until the
- * query from which it derived is destroyed).
- *
- * Typical usage might be:
- *
- *     notmuch_message_t *message;
- *     notmuch_tags_t *tags;
- *     const char *tag;
- *
- *     message = notmuch_database_find_message (database, message_id);
- *
- *     for (tags = notmuch_message_get_tags (message);
- *          notmuch_tags_valid (tags);
- *          notmuch_result_move_to_next (tags))
- *     {
- *         tag = notmuch_tags_get (tags);
- *         ....
- *     }
- *
- *     notmuch_message_destroy (message);
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_tags_t object. (For consistency, we do provide a
- * notmuch_tags_destroy function, but there's no good reason to call
- * it if the message is about to be destroyed).
- */
-func (self *Message) GetTags() *Tags {
-       if self.message == nil {
-               return nil
-       }
-       tags := C.notmuch_message_get_tags(self.message)
-       if tags == nil {
-               return nil
-       }
-       return &Tags{tags: tags}
-}
-
-/* The longest possible tag value. */
-const TAG_MAX = 200
-
-/* Add a tag to the given message.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message
- *
- * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
- *
- * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
- *     (exceeds NOTMUCH_TAG_MAX)
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so message cannot be modified.
- */
-func (self *Message) AddTag(tag string) Status {
-       if self.message == nil {
-               return STATUS_NULL_POINTER
-       }
-       c_tag := C.CString(tag)
-       defer C.free(unsafe.Pointer(c_tag))
-
-       return Status(C.notmuch_message_add_tag(self.message, c_tag))
-}
-
-/* Remove a tag from the given message.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message
- *
- * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
- *
- * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
- *     (exceeds NOTMUCH_TAG_MAX)
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so message cannot be modified.
- */
-func (self *Message) RemoveTag(tag string) Status {
-       if self.message == nil {
-               return STATUS_NULL_POINTER
-       }
-       c_tag := C.CString(tag)
-       defer C.free(unsafe.Pointer(c_tag))
-
-       return Status(C.notmuch_message_remove_tag(self.message, c_tag))
-}
-
-/* Remove all tags from the given message.
- *
- * See notmuch_message_freeze for an example showing how to safely
- * replace tag values.
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so message cannot be modified.
- */
-func (self *Message) RemoveAllTags() Status {
-       if self.message == nil {
-               return STATUS_NULL_POINTER
-       }
-       return Status(C.notmuch_message_remove_all_tags(self.message))
-}
-
-/* Freeze the current state of 'message' within the database.
- *
- * This means that changes to the message state, (via
- * notmuch_message_add_tag, notmuch_message_remove_tag, and
- * notmuch_message_remove_all_tags), will not be committed to the
- * database until the message is thawed with notmuch_message_thaw.
- *
- * Multiple calls to freeze/thaw are valid and these calls will
- * "stack". That is there must be as many calls to thaw as to freeze
- * before a message is actually thawed.
- *
- * The ability to do freeze/thaw allows for safe transactions to
- * change tag values. For example, explicitly setting a message to
- * have a given set of tags might look like this:
- *
- *    notmuch_message_freeze (message);
- *
- *    notmuch_message_remove_all_tags (message);
- *
- *    for (i = 0; i < NUM_TAGS; i++)
- *        notmuch_message_add_tag (message, tags[i]);
- *
- *    notmuch_message_thaw (message);
- *
- * With freeze/thaw used like this, the message in the database is
- * guaranteed to have either the full set of original tag values, or
- * the full set of new tag values, but nothing in between.
- *
- * Imagine the example above without freeze/thaw and the operation
- * somehow getting interrupted. This could result in the message being
- * left with no tags if the interruption happened after
- * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Message successfully frozen.
- *
- * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
- *     mode so message cannot be modified.
- */
-func (self *Message) Freeze() Status {
-       if self.message == nil {
-               return STATUS_NULL_POINTER
-       }
-       return Status(C.notmuch_message_freeze(self.message))
-}
-
-/* Thaw the current 'message', synchronizing any changes that may have
- * occurred while 'message' was frozen into the notmuch database.
- *
- * See notmuch_message_freeze for an example of how to use this
- * function to safely provide tag changes.
- *
- * Multiple calls to freeze/thaw are valid and these calls with
- * "stack". That is there must be as many calls to thaw as to freeze
- * before a message is actually thawed.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least
- *     its frozen count has successfully been reduced by 1).
- *
- * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw
- *     an unfrozen message. That is, there have been an unbalanced
- *     number of calls to notmuch_message_freeze and
- *     notmuch_message_thaw.
- */
-func (self *Message) Thaw() Status {
-       if self.message == nil {
-               return STATUS_NULL_POINTER
-       }
-
-       return Status(C.notmuch_message_thaw(self.message))
-}
-
-/* Destroy a notmuch_message_t object.
- *
- * It can be useful to call this function in the case of a single
- * query object with many messages in the result, (such as iterating
- * over the entire database). Otherwise, it's fine to never call this
- * function and there will still be no memory leaks. (The memory from
- * the messages get reclaimed when the containing query is destroyed.)
- */
-func (self *Message) Destroy() {
-       if self.message == nil {
-               return
-       }
-       C.notmuch_message_destroy(self.message)
-}
-
-/* Is the given 'tags' iterator pointing at a valid tag.
- *
- * When this function returns TRUE, notmuch_tags_get will return a
- * valid string. Whereas when this function returns FALSE,
- * notmuch_tags_get will return NULL.
- *
- * See the documentation of notmuch_message_get_tags for example code
- * showing how to iterate over a notmuch_tags_t object.
- */
-func (self *Tags) Valid() bool {
-       if self.tags == nil {
-               return false
-       }
-       v := C.notmuch_tags_valid(self.tags)
-       if v == 0 {
-               return false
-       }
-       return true
-}
-
-/* Get the current tag from 'tags' as a string.
- *
- * Note: The returned string belongs to 'tags' and has a lifetime
- * identical to it (and the query to which it ultimately belongs).
- *
- * See the documentation of notmuch_message_get_tags for example code
- * showing how to iterate over a notmuch_tags_t object.
- */
-func (self *Tags) Get() string {
-       if self.tags == nil {
-               return ""
-       }
-       s := C.notmuch_tags_get(self.tags)
-       // we dont own 's'
-
-       return C.GoString(s)
-}
-func (self *Tags) String() string {
-       return self.Get()
-}
-
-/* Move the 'tags' iterator to the next tag.
- *
- * If 'tags' is already pointing at the last tag then the iterator
- * will be moved to a point just beyond that last tag, (where
- * notmuch_tags_valid will return FALSE and notmuch_tags_get will
- * return NULL).
- *
- * See the documentation of notmuch_message_get_tags for example code
- * showing how to iterate over a notmuch_tags_t object.
- */
-func (self *Tags) MoveToNext() {
-       if self.tags == nil {
-               return
-       }
-       C.notmuch_tags_move_to_next(self.tags)
-}
-
-/* Destroy a notmuch_tags_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_tags_t object will be reclaimed when the containing
- * message or query objects are destroyed.
- */
-func (self *Tags) Destroy() {
-       if self.tags == nil {
-               return
-       }
-       C.notmuch_tags_destroy(self.tags)
-}
-
-// TODO: wrap notmuch_directory_<fct>
-
-/* Destroy a notmuch_directory_t object. */
-func (self *Directory) Destroy() {
-       if self.dir == nil {
-               return
-       }
-       C.notmuch_directory_destroy(self.dir)
-}
-
-// TODO: wrap notmuch_filenames_<fct>
-
-/* Destroy a notmuch_filenames_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_filenames_t object will be reclaimed when the
- * containing directory object is destroyed.
- *
- * It is acceptable to pass NULL for 'filenames', in which case this
- * function will do nothing.
- */
-func (self *Filenames) Destroy() {
-       if self.fnames == nil {
-               return
-       }
-       C.notmuch_filenames_destroy(self.fnames)
-}
-
-/* EOF */
index b20ae071456a5205f1a99dce2f320cb45cfeb89a..fe7a21819d67f3ad0be98573675e998971c7f834 100644 (file)
@@ -2,7 +2,7 @@ notmuch -- The python interface to notmuch
 ==========================================
 
 This module makes the functionality of the notmuch library
-(`http://notmuchmail.org`_) available to python. Successful import of
+(`https://notmuchmail.org`_) available to python. Successful import of
 this modul depends on a libnotmuch.so|dll being available on the
 user's system.
 
@@ -10,7 +10,7 @@ If you have downloaded the full source tarball, you can create the
 documentation with sphinx installed, go to the docs directory and
 "make html". A static version of the documentation is available at:
 
-  https://notmuch.readthedocs.org/projects/notmuch-python/
+  https://notmuch.readthedocs.io/projects/notmuch-python/
 
 To build the python bindings, do
 
index 94a9ed024d3859793618152ea559a168bbcbb5e2..2a000655e93f84c8d458d702e13a0f682a45cf45 100644 (file)
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 Also add information on how to contact you by electronic and paper mail.
 
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
   You should also get your employer (if you work as a programmer) or school,
 if any, to sign a "copyright disclaimer" for the program, if necessary.
 For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
+<https://www.gnu.org/licenses/>.
 
   The GNU General Public License does not permit incorporating your program
 into proprietary programs.  If your program is a subroutine library, you
 may consider it more useful to permit linking proprietary applications with
 the library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.  But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+<https://www.gnu.org/philosophy/why-not-lgpl.html>.
index 1cece5f70f25d45c9e4463a25c1145666c711a78..bef7e60d3339e290dccfd9f94f94969332805318 100644 (file)
@@ -4,7 +4,7 @@ Welcome to :mod:`notmuch`'s documentation
 .. currentmodule:: notmuch
 
 The :mod:`notmuch` module provides an interface to the `notmuch
-<http://notmuchmail.org>`_ functionality, directly interfacing to a
+<https://notmuchmail.org>`_ functionality, directly interfacing to a
 shared notmuch library.  Within :mod:`notmuch`, the classes
 :class:`Database`, :class:`Query` provide most of the core
 functionality, returning :class:`Threads`, :class:`Messages` and
index 29416a5b25bc014716b55d55771370065e2c1252..cf627ffa288cbf64d13c223d5c5d3bc586838397 100644 (file)
@@ -47,7 +47,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index daa268c12cfb2de49a796d2f70c074498c220ff8..c931329e96972237d5f380ba2270a9dcc2d28d56 100644 (file)
@@ -16,7 +16,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 Copyright 2012 Justus Winter <4winter@informatik.uni-hamburg.de>
index f30453345e0dabd6a1cb4fa81596c50bdac65143..67fb1c41142428ed6c06e36a985de8f1f784a1db 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index 3b0a525dc9083e2c6bcb6c019fa729f42d389fd7..7f86b1ac15588aac8836d3bd31dcb5a3a945dc0c 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index abca51d738516226a1fce04366f54cb8b362cd83..b7684ef614a13c1a661252df4e70e81e00c8f447 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index f8f383e4fd5370d2ad3b801ec36ae4ae427ee6c1..29f4fdf60d4b63650f00e16d22c0349c508618de 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index 6872a2916a72b1fffa6c9df72c633440f48e2e57..b1eec2cfccead788d3e8aee6c18ca9b3ba6ac528 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index d1c1b58c4a9a0cc129830fdcec2eceee93821d60..fc177eb2b1557cb68c7c5c17bcedaf974b563869 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
                Jesse Rosenthal <jrosenthal@jhu.edu>
@@ -572,7 +572,7 @@ class Message(Python3StringMixIn):
             notmuch.STATUS.SUCCESS here. See there for details."""
         if not self._msg:
             raise NotInitializedError()
-        return Message._tags_to_maildir_flags(self._msg)
+        return Message._maildir_flags_to_tags(self._msg)
 
     def __repr__(self):
         """Represent a Message() object by str()"""
index 76100ffbc3b0b14dd42f3b1e4198f5327ee5ae1b..e17f1507ec9032299535e47222c706d55688e271 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
                Jesse Rosenthal <jrosenthal@jhu.edu>
index 4327007223dcb0d5b52a317760bc988401e32244..a0f4f64ba4b37eb85edf337f4e0807fd6cdae77e 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index 1d52345794fcfef072d572ae04cfd37fa05dcab5..3b4a56accb1a2c7c7d3fb2351d078d1109f02ae1 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index 0454dbd4dd458b1f216e507f8cf61e21a0cfb976..cc151f7e2142eb4bbce751157e2a659497d647aa 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index a550523fae8902636ddbb9c141ea53660158503f..86f1f2ccc6da235bc389e050d9fb9619a3ffe726 100644 (file)
@@ -12,7 +12,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
index f4c338e3e12141acf1c362a3af2105ce9e7f8b01..d986f0c6651ee14ef9b4607b7668b82acde7fbdc 100644 (file)
@@ -14,7 +14,7 @@ FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.
 
 You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
 """
@@ -34,21 +34,21 @@ setup(name='notmuch',
       description='Python binding of the notmuch mail search and indexing library.',
       author='Sebastian Spaeth',
       author_email='Sebastian@SSpaeth.de',
-      url='http://notmuchmail.org/',
-      download_url='http://notmuchmail.org/releases/notmuch-%s.tar.gz' % __VERSION__,
+      url='https://notmuchmail.org/',
+      download_url='https://notmuchmail.org/releases/notmuch-%s.tar.gz' % __VERSION__,
       packages=['notmuch'],
       keywords=['library', 'email'],
       long_description='''Overview
 ========
 
 The notmuch module provides an interface to the `notmuch
-<http://notmuchmail.org>`_ functionality, directly interfacing with a
+<https://notmuchmail.org>`_ functionality, directly interfacing with a
 shared notmuch library. Notmuch provides a maildatabase that allows
 for extremely quick searching and filtering of your email according to
 various criteria.
 
 The documentation for the latest notmuch release can be `viewed
-online <http://notmuch.readthedocs.org/>`_.
+online <https://notmuch.readthedocs.io/>`_.
 
 Requirements
 ------------
@@ -66,5 +66,5 @@ python >= 2.5. It will not work on earlier python versions.
                    'Topic :: Software Development :: Libraries'
                    ],
       platforms='',
-      license='http://www.gnu.org/licenses/gpl-3.0.txt',
+      license='https://www.gnu.org/licenses/gpl-3.0.txt',
      )
index c03d7011f8406931045183a02eeb0d1395eb0107..12e6bab7b095388e356e25507146283b3da61e03 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
@@ -374,6 +374,30 @@ notmuch_rb_database_find_message_by_filename (VALUE self, VALUE pathv)
     return Qnil;
 }
 
+/*
+ * call-seq: DB.get_all_tags() => TAGS
+ *
+ * Returns a list of all tags found in the database.
+ */
+VALUE
+notmuch_rb_database_get_all_tags (VALUE self)
+{
+    notmuch_database_t *db;
+    notmuch_tags_t *tags;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    tags = notmuch_database_get_all_tags (db);
+    if (!tags) {
+       const char *msg = notmuch_database_status_string (db);
+       if (!msg)
+           msg = "Unknown notmuch error";
+
+       rb_raise (notmuch_rb_eBaseError, "%s", msg);
+    }
+    return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags);
+}
+
 /*
  * call-seq: DB.query(query) => QUERY
  *
index f4901a047923e3340e373b4bff8ded8335996323..48544ca2a4acbe57a48630957e9dd059de504f69 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
@@ -177,6 +177,9 @@ notmuch_rb_database_find_message (VALUE self, VALUE idv);
 VALUE
 notmuch_rb_database_find_message_by_filename (VALUE self, VALUE pathv);
 
+VALUE
+notmuch_rb_database_get_all_tags (VALUE self);
+
 VALUE
 notmuch_rb_database_query_create (VALUE self, VALUE qstrv);
 
index 303523c2dc69df0c27af45a74e66eb837e99aa7d..0f37b3910042426219ae7cdf7137ec1b017cfb11 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index e27859038697a5ecc07e634cf1e5711742df9376..656c58e6f40dfdc87bcbc86b6dd7b2eb2680ecab 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index ab3f22df9c8106624b7e42e28bd4a805fa925686..5556b43efde1b8f847d3ce3fde76bb72d1d44a36 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
@@ -229,6 +229,7 @@ Init_notmuch (void)
                      notmuch_rb_database_find_message, 1); /* in database.c */
     rb_define_method (notmuch_rb_cDatabase, "find_message_by_filename",
                      notmuch_rb_database_find_message_by_filename, 1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "all_tags", notmuch_rb_database_get_all_tags, 0); /* in database.c */
     rb_define_method (notmuch_rb_cDatabase, "query", notmuch_rb_database_query_create, 1); /* in database.c */
 
     /*
index 4ff6097faf7795397c742f5b1c64fb2cb5eb30c8..c55cf6e20d11b8faf3e10d979ad43a79952499db 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index 443a30c989c1aeb13e6443cd5863756318411018..a337feeb499fff177e7f3c6659373eaf0c004a00 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index 8cbc73f27e42fbd7cbe99d56ca85798f09d8c4a0..ce66926cfe7428b0c825fcb388d69d898f332aa4 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index b11fb9fb9c1ecf34d7e22b0b9f0d6a862fd1d32b..a0f8863399a7bf08aec00bb326f611af1a2b1f09 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index e8226ad7433f71c1a73e37ef3cf1a0d417228d5b..db8b4cfc86f0b3eaa818ce0d7876862158826862 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index 56616d9fcf1446c954cdc6608b6d1d8a9ab2e9a2..9b295981801779e1d970972d595b60eec1c4d5c6 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index 3e1fbf5d01558775017334ede827447cfd7759ce..ed403a8f1f3db30e88d8fd030991ae1b62b1c9e6 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ali Polatel <alip@exherbo.org>
  */
index 634d505b764ed4e07acefa62c7bf8c3c11012211..88bc4df40cf1b40c37fa81b73e9e62dd4f09c2fb 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 8450a17df90b9a656f0b438a3ccc0216cb56e4a3..1945b5bf1bed7ea6f20c12398a7c82948b68fbdd 100644 (file)
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  */
 
 #ifndef FUNCTION_ATTRIBUTES_H
index e200c189058c4e9dfcb51958272d03a7b057bbd2..89805c726f32424941b890a8e6fd76e9b56ab7c9 100644 (file)
@@ -9,7 +9,7 @@ notmuch-completion.bash
   bash-completion package [1] version 2.0, which depends on bash
   version 3.2 or later.
 
-  [1] http://bash-completion.alioth.debian.org/
+  [1] https://github.com/scop/bash-completion
 
 notmuch-completion.zsh
 
index cc5839247c6cd0a044db8def1c7ada02f23355c8..78047b5f424de5bf9b6d55548d6c960e1bbceb8f 100644 (file)
@@ -3,7 +3,7 @@
 # Copyright Â© 2013 Jani Nikula
 #
 # Based on the bash-completion package:
-# http://bash-completion.alioth.debian.org/
+# https://github.com/scop/bash-completion
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see http://www.gnu.org/licenses/ .
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
 #
 # Author: Jani Nikula <jani@nikula.org>
 #
index 4fc31ccf8e79264b96ecb96d92794f295eb0205d..fa4c5b5ee3d3dd3051d31190edab142fb87f3f1a 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,5 +1,7 @@
 #! /bin/sh
 
+set -u
+
 # Test whether this shell is capable of parameter substring processing.
 ( option='a/b'; : ${option#*/} ) 2>/dev/null || {
     echo "
@@ -66,10 +68,12 @@ PYTHON=${PYTHON:-}
 PREFIX=/usr/local
 LIBDIR=
 WITH_DOCS=1
+WITH_API_DOCS=1
 WITH_EMACS=1
 WITH_BASH=1
 WITH_RUBY=1
 WITH_ZSH=1
+WITH_RETRY_LOCK=1
 
 usage ()
 {
@@ -134,10 +138,12 @@ Some features can be disabled (--with-feature=no is equivalent to
 --without-feature) :
 
        --without-bash-completion       Do not install bash completions files
-       --without-docs                  Do not install documentation and man pages
+       --without-docs                  Do not install documentation
+       --without-api-docs              Do not install API man page
        --without-emacs                 Do not install lisp file
        --without-ruby                  Do not install ruby bindings
        --without-zsh-completion        Do not install zsh completions files
+       --without-retry-lock            Do not use blocking xapian opens, even if available
 
 Additional options are accepted for compatibility with other
 configure-script calling conventions, but don't do anything yet:
@@ -180,11 +186,21 @@ for option; do
     elif [ "${option%%=*}" = '--with-docs' ]; then
        if [ "${option#*=}" = 'no' ]; then
            WITH_DOCS=0
+           WITH_API_DOCS=0
        else
            WITH_DOCS=1
        fi
     elif [ "${option}" = '--without-docs' ] ; then
        WITH_DOCS=0
+       WITH_API_DOCS=0
+    elif [ "${option%%=*}" = '--with-api-docs' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_API_DOCS=0
+       else
+           WITH_API_DOCS=1
+       fi
+    elif [ "${option}" = '--without-api-docs' ] ; then
+       WITH_API_DOCS=0
     elif [ "${option%%=*}" = '--with-emacs' ]; then
        if [ "${option#*=}" = 'no' ]; then
            WITH_EMACS=0
@@ -209,6 +225,14 @@ for option; do
        fi
     elif [ "${option}" = '--without-ruby' ] ; then
        WITH_RUBY=0
+    elif [ "${option%%=*}" = '--with-retry-lock' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_RETRY_LOCK=0
+       else
+           WITH_RETRY_LOCK=1
+       fi
+    elif [ "${option}" = '--without-retry-lock' ] ; then
+       WITH_RETRY_LOCK=0
     elif [ "${option%%=*}" = '--with-zsh-completion' ]; then
        if [ "${option#*=}" = 'no' ]; then
            WITH_ZSH=0
@@ -250,7 +274,7 @@ if [ -z "$LIBDIR" ] ; then
     libdir_expanded="${PREFIX}/lib"
 else
     # very non-general variable expansion
-    libdir_expanded=`echo "$LIBDIR" | sed "s|\\${prefix}|${PREFIX}|g; s|\\$prefix/|${PREFIX}/|; s|//*|/|g"`
+    libdir_expanded=$(echo "$LIBDIR" | sed "s|\\${prefix}|${PREFIX}|g; s|\\$prefix/|${PREFIX}/|; s|//*|/|g")
 fi
 
 cat <<EOF
@@ -356,23 +380,58 @@ if [ ${have_xapian} = "0" ]; then
     errors=$((errors + 1))
 fi
 
-# Compaction is only supported on Xapian > 1.2.6
 have_xapian_compact=0
+have_xapian_field_processor=0
 if [ ${have_xapian} = "1" ]; then
     printf "Checking for Xapian compaction support... "
-    case "${xapian_version}" in
-       0.*|1.[01].*|1.2.[0-5])
-           printf "No (only available with Xapian > 1.2.6).\n" ;;
-       [1-9]*.[0-9]*.[0-9]*)
-           have_xapian_compact=1
-           printf "Yes.\n" ;;
-       *)
-           printf "Unknown version.\n" ;;
-    esac
-fi
+    cat>_compact.cc<<EOF
+#include <xapian.h>
+class TestCompactor : public Xapian::Compactor { };
+EOF
+    if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _compact.cc -o _compact.o > /dev/null 2>&1
+    then
+       have_xapian_compact=1
+       printf "Yes.\n"
+    else
+       printf "No.\n"
+    fi
+
+    rm -f _compact.o _compact.cc
+
+    printf "Checking for Xapian FieldProcessor API... "
+    cat>_field_processor.cc<<EOF
+#include <xapian.h>
+class TitleFieldProcessor : public Xapian::FieldProcessor { };
+EOF
+    if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _field_processor.cc -o _field_processor.o > /dev/null 2>&1
+    then
+       have_xapian_field_processor=1
+       printf "Yes.\n"
+    else
+       printf "No. (optional)\n"
+    fi
+
+    rm -f _field_processor.o _field_processor.cc
+
+    default_xapian_backend=""
+    # DB_RETRY_LOCK is only supported on Xapian > 1.3.2
+    have_xapian_db_retry_lock=0
+    if [ $WITH_RETRY_LOCK = "1" ]; then
+       printf "Checking for Xapian lock retry support... "
+       cat>_retry.cc<<EOF
+#include <xapian.h>
+int flag = Xapian::DB_RETRY_LOCK;
+EOF
+       if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _retry.cc -o _retry.o > /dev/null 2>&1
+       then
+           have_xapian_db_retry_lock=1
+           printf "Yes.\n"
+       else
+           printf "No. (optional)\n"
+       fi
+       rm -f _retry.o _retry.cc
+    fi
 
-default_xapian_backend=""
-if [ ${have_xapian} = "1" ]; then
     printf "Testing default Xapian backend... "
     cat >_default_backend.cc <<EOF
 #include <xapian.h>
@@ -380,16 +439,17 @@ int main(int argc, char** argv) {
    Xapian::WritableDatabase db("test.db",Xapian::DB_CREATE_OR_OPEN);
 }
 EOF
-    ${CXX} ${CXXLAGS} ${xapian_cxxflags} _default_backend.cc -o _default_backend ${xapian_ldflags}
+    ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} _default_backend.cc -o _default_backend ${xapian_ldflags}
     ./_default_backend
     if [ -f test.db/iamglass ]; then
        default_xapian_backend=glass
     else
        default_xapian_backend=chert
     fi
-    printf "${default_xapian_backend}\n";
+    printf "%s\n" "${default_xapian_backend}";
     rm -rf test.db _default_backend _default_backend.cc
 fi
+
 # we need to have a version >= 2.6.5 to avoid a crypto bug. We need
 # 2.6.7 for permissive "From " header handling.
 GMIME_MINVER=2.6.7
@@ -413,8 +473,9 @@ have_glib=0
 if pkg-config --exists 'glib-2.0 >= 2.22'; then
     printf "Yes.\n"
     have_glib=1
-    glib_cflags=$(pkg-config --cflags glib-2.0)
-    glib_ldflags=$(pkg-config --libs glib-2.0)
+    # these are included in gmime cflags and ldflags
+    # glib_cflags=$(pkg-config --cflags glib-2.0)
+    # glib_ldflags=$(pkg-config --libs glib-2.0)
 else
     printf "No.\n"
     errors=$((errors + 1))
@@ -461,7 +522,7 @@ for name in ${PYTHON} python python2 python3; do
     if command -v $name > /dev/null; then
        have_python=1
        python=$name
-       printf "Yes ($name).\n"
+       printf "Yes (%s).\n" "$name"
        break
     fi
 done
@@ -479,6 +540,7 @@ if pkg-config --exists valgrind; then
 else
     printf "No (but that's fine).\n"
     have_valgrind=0
+    valgrind_cflags=
 fi
 
 printf "Checking for bash-completion (>= 1.90)... "
@@ -489,12 +551,12 @@ else
     WITH_BASH=0
 fi
 
-if [ -z "${EMACSLISPDIR}" ]; then
-    EMACSLISPDIR='$(prefix)/share/emacs/site-lisp'
+if [ -z "${EMACSLISPDIR-}" ]; then
+    EMACSLISPDIR="\$(prefix)/share/emacs/site-lisp"
 fi
 
-if [ -z "${EMACSETCDIR}" ]; then
-    EMACSETCDIR='$(prefix)/share/emacs/site-lisp'
+if [ -z "${EMACSETCDIR-}" ]; then
+    EMACSETCDIR="\$(prefix)/share/emacs/site-lisp"
 fi
 
 printf "Checking if emacs is available... "
@@ -507,7 +569,7 @@ else
 fi
 
 have_doxygen=0
-if [ $WITH_DOCS = "1" ] ; then
+if [ $WITH_API_DOCS = "1" ] ; then
     printf "Checking if doxygen is available... "
     if command -v doxygen > /dev/null; then
        printf "Yes.\n"
@@ -542,7 +604,7 @@ fi
 libdir_in_ldconfig=0
 
 printf "Checking which platform we are on... "
-uname=`uname`
+uname=$(uname)
 if [ $uname = "Darwin" ] ; then
     printf "Mac OS X.\n"
     platform=MACOSX
@@ -560,11 +622,11 @@ elif [ $uname = "OpenBSD" ] ; then
     platform=OPENBSD
     linker_resolves_library_dependencies=0
 elif [ $uname = "Linux" ] || [ $uname = "GNU" ] ; then
-    printf "$uname\n"
+    printf "%s\n" "$uname"
     platform="$uname"
     linker_resolves_library_dependencies=1
 
-    printf "Checking for $libdir_expanded in ldconfig... "
+    printf "Checking for %s in ldconfig... " "$libdir_expanded"
     ldconfig_paths=$(/sbin/ldconfig -N -X -v 2>/dev/null | sed -n -e 's,^\(/.*\):\( (.*)\)\?$,\1,p')
     # Separate ldconfig_paths only on newline (not on any potential
     # embedded space characters in any filenames). Note, we use a
@@ -621,7 +683,7 @@ EOF
     fi
     if [ $have_xapian -eq 0 ]; then
        echo "  Xapian library (including development files such as headers)"
-       echo "  http://xapian.org/"
+       echo "  https://xapian.org/"
     fi
     if [ $have_zlib -eq 0 ]; then
        echo "  zlib library (>= version 1.2.5.2, including development files such as headers)"
@@ -641,7 +703,7 @@ EOF
     fi
     if [ $have_talloc -eq 0 ]; then
        echo "  The talloc library (including development files such as headers)"
-       echo "  http://talloc.samba.org/"
+       echo "  https://talloc.samba.org/"
        echo
     fi
     cat <<EOF
@@ -815,7 +877,7 @@ for flag in -Wall -Wextra -Wwrite-strings; do
        WARN_CXXFLAGS="${WARN_CXXFLAGS}${WARN_CXXFLAGS:+ }${flag}"
     fi
 done
-printf "\n\t${WARN_CXXFLAGS}\n"
+printf "\n\t%s\n" "${WARN_CXXFLAGS}"
 
 WARN_CFLAGS="${WARN_CXXFLAGS}"
 printf "Checking for available C compiler warning flags... "
@@ -825,7 +887,7 @@ for flag in -Wmissing-declarations; do
        WARN_CFLAGS="${WARN_CFLAGS}${WARN_CFLAGS:+ }${flag}"
     fi
 done
-printf "\n\t${WARN_CFLAGS}\n"
+printf "\n\t%s\n" "${WARN_CFLAGS}"
 
 rm -f minimal minimal.c _libversion.c _libversion _libversion.sh
 
@@ -997,6 +1059,12 @@ HAVE_D_TYPE = ${have_d_type}
 # Whether the Xapian version in use supports compaction
 HAVE_XAPIAN_COMPACT = ${have_xapian_compact}
 
+# Whether the Xapian version in use supports field processors
+HAVE_XAPIAN_FIELD_PROCESSOR = ${have_xapian_field_processor}
+
+# Whether the Xapian version in use supports DB_RETRY_LOCK
+HAVE_XAPIAN_DB_RETRY_LOCK = ${have_xapian_db_retry_lock}
+
 # Whether the getpwuid_r function is standards-compliant
 # (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS
 # to enable the standards-compliant version -- needed for Solaris)
@@ -1059,33 +1127,25 @@ WITH_BASH = ${WITH_BASH}
 WITH_ZSH = ${WITH_ZSH}
 
 # Combined flags for compiling and linking against all of the above
-CONFIGURE_CFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)      \\
-                  -DHAVE_CANONICALIZE_FILE_NAME=\$(HAVE_CANONICALIZE_FILE_NAME) \\
-                  \$(ZLIB_CFLAGS)                                       \\
-                  \$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND)   \\
-                  \$(VALGRIND_CFLAGS)                                   \\
-                  -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)                 \\
-                  -DHAVE_STRSEP=\$(HAVE_STRSEP)                         \\
-                  -DHAVE_TIMEGM=\$(HAVE_TIMEGM)                         \\
-                  -DHAVE_D_TYPE=\$(HAVE_D_TYPE)                         \\
-                  -DSTD_GETPWUID=\$(STD_GETPWUID)                       \\
-                  -DSTD_ASCTIME=\$(STD_ASCTIME)                         \\
-                  -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)         \\
-                  -DUTIL_BYTE_ORDER=\$(UTIL_BYTE_ORDER)
-
-CONFIGURE_CXXFLAGS = -DHAVE_GETLINE=\$(HAVE_GETLINE) \$(GMIME_CFLAGS)    \\
-                    -DHAVE_CANONICALIZE_FILE_NAME=\$(HAVE_CANONICALIZE_FILE_NAME) \\
-                    \$(ZLIB_CFLAGS)                                     \\
-                    \$(TALLOC_CFLAGS) -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \\
-                    \$(VALGRIND_CFLAGS) \$(XAPIAN_CXXFLAGS)             \\
-                    -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)               \\
-                    -DHAVE_STRSEP=\$(HAVE_STRSEP)                       \\
-                    -DHAVE_TIMEGM=\$(HAVE_TIMEGM)                       \\
-                    -DHAVE_D_TYPE=\$(HAVE_D_TYPE)                       \\
-                    -DSTD_GETPWUID=\$(STD_GETPWUID)                     \\
-                    -DSTD_ASCTIME=\$(STD_ASCTIME)                       \\
-                    -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)       \\
-                    -DUTIL_BYTE_ORDER=\$(UTIL_BYTE_ORDER)
+COMMON_CONFIGURE_CFLAGS = \\
+       \$(GMIME_CFLAGS) \$(TALLOC_CFLAGS) \$(ZLIB_CFLAGS)      \\
+       -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \$(VALGRIND_CFLAGS)   \\
+       -DHAVE_GETLINE=\$(HAVE_GETLINE)                         \\
+       -DHAVE_CANONICALIZE_FILE_NAME=\$(HAVE_CANONICALIZE_FILE_NAME) \\
+       -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)                   \\
+       -DHAVE_STRSEP=\$(HAVE_STRSEP)                           \\
+       -DHAVE_TIMEGM=\$(HAVE_TIMEGM)                           \\
+       -DHAVE_D_TYPE=\$(HAVE_D_TYPE)                           \\
+       -DSTD_GETPWUID=\$(STD_GETPWUID)                         \\
+       -DSTD_ASCTIME=\$(STD_ASCTIME)                           \\
+       -DHAVE_XAPIAN_COMPACT=\$(HAVE_XAPIAN_COMPACT)           \\
+       -DHAVE_XAPIAN_FIELD_PROCESSOR=\$(HAVE_XAPIAN_FIELD_PROCESSOR) \\
+       -DHAVE_XAPIAN_DB_RETRY_LOCK=\$(HAVE_XAPIAN_DB_RETRY_LOCK) \\
+       -DUTIL_BYTE_ORDER=\$(UTIL_BYTE_ORDER)
+
+CONFIGURE_CFLAGS = \$(COMMON_CONFIGURE_CFLAGS)
+
+CONFIGURE_CXXFLAGS = \$(COMMON_CONFIGURE_CFLAGS) \$(XAPIAN_CXXFLAGS)
 
 CONFIGURE_LDFLAGS =  \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS)
 EOF
@@ -1098,6 +1158,12 @@ cat > sh.config <<EOF
 # Whether the Xapian version in use supports compaction
 NOTMUCH_HAVE_XAPIAN_COMPACT=${have_xapian_compact}
 
+# Whether the Xapian version in use supports field processors
+NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR=${have_xapian_field_processor}
+
+# Whether the Xapian version in use supports lock retry
+NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${have_xapian_db_retry_lock}
+
 # Which backend will Xapian use by default?
 NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend}
 
diff --git a/contrib/go/.gitignore b/contrib/go/.gitignore
new file mode 100644 (file)
index 0000000..c394479
--- /dev/null
@@ -0,0 +1,3 @@
+src/github.com/
+pkg/
+bin/
diff --git a/contrib/go/LICENSE b/contrib/go/LICENSE
new file mode 100644 (file)
index 0000000..4362b49
--- /dev/null
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/contrib/go/Makefile b/contrib/go/Makefile
new file mode 100644 (file)
index 0000000..1b9e750
--- /dev/null
@@ -0,0 +1,40 @@
+# Makefile for the go bindings of notmuch
+
+export GOPATH      ?= $(shell pwd)
+export CGO_CFLAGS  ?= -I../../../../lib
+export CGO_LDFLAGS ?= -L../../../../lib
+
+GO         ?= go
+GOFMT      ?= gofmt
+
+all: notmuch notmuch-addrlookup
+
+.PHONY: notmuch
+notmuch:
+       $(GO) install notmuch
+
+.PHONY: goconfig
+goconfig:
+       if [ ! -d github.com/msbranco/goconfig ]; then \
+           $(GO) get github.com/msbranco/goconfig; \
+       fi
+
+.PHONY: notmuch-addrlookup
+notmuch-addrlookup: notmuch goconfig
+       $(GO) install notmuch-addrlookup
+
+.PHONY: format
+format:
+       $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch
+       $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch-addrlookup
+
+.PHONY: check-format
+check-format:
+       $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch
+       $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch-addrlookup
+
+.PHONY: clean
+clean:
+       $(GO) clean notmuch
+       $(GO) clean notmuch-addrlookup
+       rm -rf pkg bin
diff --git a/contrib/go/README b/contrib/go/README
new file mode 100644 (file)
index 0000000..1825ae0
--- /dev/null
@@ -0,0 +1,16 @@
+go-notmuch
+==========
+
+simple go bindings to the libnotmuch library[1].
+
+they are heavily inspired from the vala bindings from Sebastian Spaeth: 
+  https://github.com/spaetz/vala-notmuch
+
+note: the whole library hasn't been wrapped (yet?)
+
+todo
+----
+
+ - improve notmuch-addrlookup regexp
+
+[1] https://notmuchmail.org/
diff --git a/contrib/go/src/notmuch-addrlookup/addrlookup.go b/contrib/go/src/notmuch-addrlookup/addrlookup.go
new file mode 100644 (file)
index 0000000..916e5bb
--- /dev/null
@@ -0,0 +1,261 @@
+package main
+
+// stdlib imports
+import "os"
+import "path"
+import "log"
+import "fmt"
+import "regexp"
+import "strings"
+import "sort"
+
+// 3rd-party imports
+import "notmuch"
+import "github.com/msbranco/goconfig"
+
+type mail_addr_freq struct {
+       addr  string
+       count [3]uint
+}
+
+type frequencies map[string]uint
+
+/* Used to sort the email addresses from most to least used */
+func sort_by_freq(m1, m2 *mail_addr_freq) int {
+       if m1.count[0] == m2.count[0] &&
+               m1.count[1] == m2.count[1] &&
+               m1.count[2] == m2.count[2] {
+               return 0
+       }
+
+       if m1.count[0] > m2.count[0] ||
+               m1.count[0] == m2.count[0] &&
+                       m1.count[1] > m2.count[1] ||
+               m1.count[0] == m2.count[0] &&
+                       m1.count[1] == m2.count[1] &&
+                       m1.count[2] > m2.count[2] {
+               return -1
+       }
+
+       return 1
+}
+
+type maddresses []*mail_addr_freq
+
+func (self *maddresses) Len() int {
+       return len(*self)
+}
+
+func (self *maddresses) Less(i, j int) bool {
+       m1 := (*self)[i]
+       m2 := (*self)[j]
+       v := sort_by_freq(m1, m2)
+       if v <= 0 {
+               return true
+       }
+       return false
+}
+
+func (self *maddresses) Swap(i, j int) {
+       (*self)[i], (*self)[j] = (*self)[j], (*self)[i]
+}
+
+// find most frequent real name for each mail address
+func frequent_fullname(freqs frequencies) string {
+       var maxfreq uint = 0
+       fullname := ""
+       freqs_sz := len(freqs)
+
+       for mail, freq := range freqs {
+               if (freq > maxfreq && mail != "") || freqs_sz == 1 {
+                       // only use the entry if it has a real name
+                       // or if this is the only entry
+                       maxfreq = freq
+                       fullname = mail
+               }
+       }
+       return fullname
+}
+
+func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies {
+
+       freqs := make(frequencies)
+
+       pattern := `\s*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
+       // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
+       //      "<?(?P<mail>\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)"
+       pattern = `.*` + strings.ToLower(name) + `.*`
+       var re *regexp.Regexp = nil
+       var err error = nil
+       if re, err = regexp.Compile(pattern); err != nil {
+               log.Printf("error: %v\n", err)
+               return &freqs
+       }
+
+       headers := []string{"from"}
+       if pass == 1 {
+               headers = append(headers, "to", "cc", "bcc")
+       }
+
+       for ; msgs.Valid(); msgs.MoveToNext() {
+               msg := msgs.Get()
+               //println("==> msg [", msg.GetMessageId(), "]")
+               for _, header := range headers {
+                       froms := strings.ToLower(msg.GetHeader(header))
+                       //println("  froms: ["+froms+"]")
+                       for _, from := range strings.Split(froms, ",") {
+                               from = strings.Trim(from, " ")
+                               match := re.FindString(from)
+                               //println("  -> match: ["+match+"]")
+                               occ, ok := freqs[match]
+                               if !ok {
+                                       freqs[match] = 0
+                                       occ = 0
+                               }
+                               freqs[match] = occ + 1
+                       }
+               }
+       }
+       return &freqs
+}
+
+func search_address_passes(queries [3]*notmuch.Query, name string) []string {
+       var val []string
+       addr_freq := make(map[string]*mail_addr_freq)
+       addr_to_realname := make(map[string]*frequencies)
+
+       var pass uint = 0 // 0-based
+       for _, query := range queries {
+               if query == nil {
+                       //println("**warning: idx [",idx,"] contains a nil query")
+                       continue
+               }
+               msgs := query.SearchMessages()
+               ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname)
+               for addr, count := range *ht {
+                       freq, ok := addr_freq[addr]
+                       if !ok {
+                               freq = &mail_addr_freq{addr: addr, count: [3]uint{0, 0, 0}}
+                       }
+                       freq.count[pass] = count
+                       addr_freq[addr] = freq
+               }
+               msgs.Destroy()
+               pass += 1
+       }
+
+       addrs := make(maddresses, len(addr_freq))
+       {
+               iaddr := 0
+               for _, freq := range addr_freq {
+                       addrs[iaddr] = freq
+                       iaddr += 1
+               }
+       }
+       sort.Sort(&addrs)
+
+       for _, addr := range addrs {
+               freqs, ok := addr_to_realname[addr.addr]
+               if ok {
+                       val = append(val, frequent_fullname(*freqs))
+               } else {
+                       val = append(val, addr.addr)
+               }
+       }
+       //println("val:",val)
+       return val
+}
+
+type address_matcher struct {
+       // the notmuch database
+       db *notmuch.Database
+       // full path of the notmuch database
+       user_db_path string
+       // user primary email
+       user_primary_email string
+       // user tag to mark from addresses as in the address book
+       user_addrbook_tag string
+}
+
+func new_address_matcher() *address_matcher {
+       // honor NOTMUCH_CONFIG
+       home := os.Getenv("NOTMUCH_CONFIG")
+       if home == "" {
+               home = os.Getenv("HOME")
+       }
+
+       cfg, err := goconfig.ReadConfigFile(path.Join(home, ".notmuch-config"))
+       if err != nil {
+               log.Fatalf("error loading config file:", err)
+       }
+
+       db_path, _ := cfg.GetString("database", "path")
+       primary_email, _ := cfg.GetString("user", "primary_email")
+       addrbook_tag, err := cfg.GetString("user", "addrbook_tag")
+       if err != nil {
+               addrbook_tag = "addressbook"
+       }
+
+       self := &address_matcher{db: nil,
+               user_db_path:       db_path,
+               user_primary_email: primary_email,
+               user_addrbook_tag:  addrbook_tag}
+       return self
+}
+
+func (self *address_matcher) run(name string) {
+       queries := [3]*notmuch.Query{}
+
+       // open the database
+       if db, status := notmuch.OpenDatabase(self.user_db_path,
+               notmuch.DATABASE_MODE_READ_ONLY); status == notmuch.STATUS_SUCCESS {
+               self.db = db
+       } else {
+               log.Fatalf("Failed to open the database: %v\n", status)
+       }
+
+       // pass 1: look at all from: addresses with the address book tag
+       query := "tag:" + self.user_addrbook_tag
+       if name != "" {
+               query = query + " and from:" + name + "*"
+       }
+       queries[0] = self.db.CreateQuery(query)
+
+       // pass 2: look at all to: addresses sent from our primary mail
+       query = ""
+       if name != "" {
+               query = "to:" + name + "*"
+       }
+       if self.user_primary_email != "" {
+               query = query + " from:" + self.user_primary_email
+       }
+       queries[1] = self.db.CreateQuery(query)
+
+       // if that leads only to a few hits, we check every from too
+       if queries[0].CountMessages()+queries[1].CountMessages() < 10 {
+               query = ""
+               if name != "" {
+                       query = "from:" + name + "*"
+               }
+               queries[2] = self.db.CreateQuery(query)
+       }
+
+       // actually retrieve and sort addresses
+       results := search_address_passes(queries, name)
+       for _, v := range results {
+               if v != "" && v != "\n" {
+                       fmt.Println(v)
+               }
+       }
+       return
+}
+
+func main() {
+       //fmt.Println("args:",os.Args)
+       app := new_address_matcher()
+       name := ""
+       if len(os.Args) > 1 {
+               name = os.Args[1]
+       }
+       app.run(name)
+}
diff --git a/contrib/go/src/notmuch/notmuch.go b/contrib/go/src/notmuch/notmuch.go
new file mode 100644 (file)
index 0000000..2d68431
--- /dev/null
@@ -0,0 +1,1404 @@
+// Wrapper around the notmuch library
+
+package notmuch
+
+/*
+#cgo LDFLAGS: -lnotmuch
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "notmuch.h"
+*/
+import "C"
+import "unsafe"
+
+// Status codes used for the return values of most functions
+type Status C.notmuch_status_t
+
+const (
+       STATUS_SUCCESS Status = iota
+       STATUS_OUT_OF_MEMORY
+       STATUS_READ_ONLY_DATABASE
+       STATUS_XAPIAN_EXCEPTION
+       STATUS_FILE_ERROR
+       STATUS_FILE_NOT_EMAIL
+       STATUS_DUPLICATE_MESSAGE_ID
+       STATUS_NULL_POINTER
+       STATUS_TAG_TOO_LONG
+       STATUS_UNBALANCED_FREEZE_THAW
+       STATUS_UNBALANCED_ATOMIC
+
+       STATUS_LAST_STATUS
+)
+
+func (self Status) String() string {
+       var p *C.char
+
+       // p is read-only
+       p = C.notmuch_status_to_string(C.notmuch_status_t(self))
+       if p != nil {
+               s := C.GoString(p)
+               return s
+       }
+       return ""
+}
+
+/* Various opaque data types. For each notmuch_<foo>_t see the various
+ * notmuch_<foo> functions below. */
+
+type Database struct {
+       db *C.notmuch_database_t
+}
+
+type Query struct {
+       query *C.notmuch_query_t
+}
+
+type Threads struct {
+       threads *C.notmuch_threads_t
+}
+
+type Thread struct {
+       thread *C.notmuch_thread_t
+}
+
+type Messages struct {
+       messages *C.notmuch_messages_t
+}
+
+type Message struct {
+       message *C.notmuch_message_t
+}
+
+type Tags struct {
+       tags *C.notmuch_tags_t
+}
+
+type Directory struct {
+       dir *C.notmuch_directory_t
+}
+
+type Filenames struct {
+       fnames *C.notmuch_filenames_t
+}
+
+type DatabaseMode C.notmuch_database_mode_t
+
+const (
+       DATABASE_MODE_READ_ONLY DatabaseMode = iota
+       DATABASE_MODE_READ_WRITE
+)
+
+// Create a new, empty notmuch database located at 'path'
+func NewDatabase(path string) (*Database, Status) {
+
+       var c_path *C.char = C.CString(path)
+       defer C.free(unsafe.Pointer(c_path))
+
+       if c_path == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       self := &Database{db: nil}
+       st := Status(C.notmuch_database_create(c_path, &self.db))
+       if st != STATUS_SUCCESS {
+               return nil, st
+       }
+       return self, st
+}
+
+/* Open an existing notmuch database located at 'path'.
+ *
+ * The database should have been created at some time in the past,
+ * (not necessarily by this process), by calling
+ * notmuch_database_create with 'path'. By default the database should be
+ * opened for reading only. In order to write to the database you need to
+ * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode.
+ *
+ * An existing notmuch database can be identified by the presence of a
+ * directory named ".notmuch" below 'path'.
+ *
+ * The caller should call notmuch_database_destroy when finished with
+ * this database.
+ *
+ * In case of any failure, this function returns NULL, (after printing
+ * an error message on stderr).
+ */
+func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) {
+
+       var c_path *C.char = C.CString(path)
+       defer C.free(unsafe.Pointer(c_path))
+
+       if c_path == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       self := &Database{db: nil}
+       st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db))
+       if st != STATUS_SUCCESS {
+               return nil, st
+       }
+       return self, st
+}
+
+/* Close the given notmuch database, freeing all associated
+ * resources. See notmuch_database_open. */
+func (self *Database) Close() Status {
+       return Status(C.notmuch_database_destroy(self.db))
+}
+
+/* Return the database path of the given database.
+ */
+func (self *Database) GetPath() string {
+
+       /* The return value is a string owned by notmuch so should not be
+        * modified nor freed by the caller. */
+       var p *C.char = C.notmuch_database_get_path(self.db)
+       if p != nil {
+               s := C.GoString(p)
+               return s
+       }
+       return ""
+}
+
+/* Return the database format version of the given database. */
+func (self *Database) GetVersion() uint {
+       return uint(C.notmuch_database_get_version(self.db))
+}
+
+/* Does this database need to be upgraded before writing to it?
+ *
+ * If this function returns TRUE then no functions that modify the
+ * database (notmuch_database_add_message, notmuch_message_add_tag,
+ * notmuch_directory_set_mtime, etc.) will work unless the function
+ * notmuch_database_upgrade is called successfully first. */
+func (self *Database) NeedsUpgrade() bool {
+       do_upgrade := C.notmuch_database_needs_upgrade(self.db)
+       if do_upgrade == 0 {
+               return false
+       }
+       return true
+}
+
+// TODO: notmuch_database_upgrade
+
+/* Retrieve a directory object from the database for 'path'.
+ *
+ * Here, 'path' should be a path relative to the path of 'database'
+ * (see notmuch_database_get_path), or else should be an absolute path
+ * with initial components that match the path of 'database'.
+ *
+ * Can return NULL if a Xapian exception occurs.
+ */
+func (self *Database) GetDirectory(path string) (*Directory, Status) {
+       var c_path *C.char = C.CString(path)
+       defer C.free(unsafe.Pointer(c_path))
+
+       if c_path == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       var c_dir *C.notmuch_directory_t
+       st := Status(C.notmuch_database_get_directory(self.db, c_path, &c_dir))
+       if st != STATUS_SUCCESS || c_dir == nil {
+               return nil, st
+       }
+       return &Directory{dir: c_dir}, st
+}
+
+/* Add a new message to the given notmuch database.
+ *
+ * Here,'filename' should be a path relative to the path of
+ * 'database' (see notmuch_database_get_path), or else should be an
+ * absolute filename with initial components that match the path of
+ * 'database'.
+ *
+ * The file should be a single mail message (not a multi-message mbox)
+ * that is expected to remain at its current location, (since the
+ * notmuch database will reference the filename, and will not copy the
+ * entire contents of the file.
+ *
+ * If 'message' is not NULL, then, on successful return '*message'
+ * will be initialized to a message object that can be used for things
+ * such as adding tags to the just-added message. The user should call
+ * notmuch_message_destroy when done with the message. On any failure
+ * '*message' will be set to NULL.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
+ *     message not added.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
+ *     ID as another message already in the database. The new
+ *     filename was successfully added to the message in the database
+ *     (if not already present).
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the
+ *     file, (such as permission denied, or file not found,
+ *     etc.). Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
+ *     like an email message. Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be added.
+ */
+func (self *Database) AddMessage(fname string) (*Message, Status) {
+       var c_fname *C.char = C.CString(fname)
+       defer C.free(unsafe.Pointer(c_fname))
+
+       if c_fname == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       var c_msg *C.notmuch_message_t = new(C.notmuch_message_t)
+       st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg))
+
+       return &Message{message: c_msg}, st
+}
+
+/* Remove a message from the given notmuch database.
+ *
+ * Note that only this particular filename association is removed from
+ * the database. If the same message (as determined by the message ID)
+ * is still available via other filenames, then the message will
+ * persist in the database for those filenames. When the last filename
+ * is removed for a particular message, the database content for that
+ * message will be entirely removed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the
+ *     message was removed from the database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
+ *     message not removed.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but
+ *     the message persists in the database with at least one other
+ *     filename.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be removed.
+ */
+func (self *Database) RemoveMessage(fname string) Status {
+
+       var c_fname *C.char = C.CString(fname)
+       defer C.free(unsafe.Pointer(c_fname))
+
+       if c_fname == nil {
+               return STATUS_OUT_OF_MEMORY
+       }
+
+       st := C.notmuch_database_remove_message(self.db, c_fname)
+       return Status(st)
+}
+
+/* Find a message with the given message_id.
+ *
+ * If the database contains a message with the given message_id, then
+ * a new notmuch_message_t object is returned. The caller should call
+ * notmuch_message_destroy when done with the message.
+ *
+ * This function returns NULL in the following situations:
+ *
+ *     * No message is found with the given message_id
+ *     * An out-of-memory situation occurs
+ *     * A Xapian exception occurs
+ */
+func (self *Database) FindMessage(message_id string) (*Message, Status) {
+
+       var c_msg_id *C.char = C.CString(message_id)
+       defer C.free(unsafe.Pointer(c_msg_id))
+
+       if c_msg_id == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       msg := &Message{message: nil}
+       st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message))
+       if st != STATUS_SUCCESS {
+               return nil, st
+       }
+       return msg, st
+}
+
+/* Return a list of all tags found in the database.
+ *
+ * This function creates a list of all tags found in the database. The
+ * resulting list contains all tags from all messages found in the database.
+ *
+ * On error this function returns NULL.
+ */
+func (self *Database) GetAllTags() *Tags {
+       tags := C.notmuch_database_get_all_tags(self.db)
+       if tags == nil {
+               return nil
+       }
+       return &Tags{tags: tags}
+}
+
+/* Create a new query for 'database'.
+ *
+ * Here, 'database' should be an open database, (see
+ * notmuch_database_open and notmuch_database_create).
+ *
+ * For the query string, we'll document the syntax here more
+ * completely in the future, but it's likely to be a specialized
+ * version of the general Xapian query syntax:
+ *
+ * https://xapian.org/docs/queryparser.html
+ *
+ * As a special case, passing either a length-zero string, (that is ""),
+ * or a string consisting of a single asterisk (that is "*"), will
+ * result in a query that returns all messages in the database.
+ *
+ * See notmuch_query_set_sort for controlling the order of results.
+ * See notmuch_query_search_messages and notmuch_query_search_threads
+ * to actually execute the query.
+ *
+ * User should call notmuch_query_destroy when finished with this
+ * query.
+ *
+ * Will return NULL if insufficient memory is available.
+ */
+func (self *Database) CreateQuery(query string) *Query {
+
+       var c_query *C.char = C.CString(query)
+       defer C.free(unsafe.Pointer(c_query))
+
+       if c_query == nil {
+               return nil
+       }
+
+       q := C.notmuch_query_create(self.db, c_query)
+       if q == nil {
+               return nil
+       }
+       return &Query{query: q}
+}
+
+/* Sort values for notmuch_query_set_sort */
+type Sort C.notmuch_sort_t
+
+const (
+       SORT_OLDEST_FIRST Sort = 0
+       SORT_NEWEST_FIRST
+       SORT_MESSAGE_ID
+       SORT_UNSORTED
+)
+
+/* Return the query_string of this query. See notmuch_query_create. */
+func (self *Query) String() string {
+       // FIXME: do we own 'q' or not ?
+       q := C.notmuch_query_get_query_string(self.query)
+       //defer C.free(unsafe.Pointer(q))
+
+       if q != nil {
+               s := C.GoString(q)
+               return s
+       }
+
+       return ""
+}
+
+/* Specify the sorting desired for this query. */
+func (self *Query) SetSort(sort Sort) {
+       C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort))
+}
+
+/* Return the sort specified for this query. See notmuch_query_set_sort. */
+func (self *Query) GetSort() Sort {
+       return Sort(C.notmuch_query_get_sort(self.query))
+}
+
+/* Execute a query for threads, returning a notmuch_threads_t object
+ * which can be used to iterate over the results. The returned threads
+ * object is owned by the query and as such, will only be valid until
+ * notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_threads_t *threads;
+ *     notmuch_thread_t *thread;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (threads = notmuch_query_search_threads (query);
+ *          notmuch_threads_valid (threads);
+ *          notmuch_threads_move_to_next (threads))
+ *     {
+ *         thread = notmuch_threads_get (threads);
+ *         ....
+ *         notmuch_thread_destroy (thread);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a thread before its containing
+ * query, you can call notmuch_thread_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your thread objects
+ * are long-lived, then you don't need to call notmuch_thread_destroy
+ * and all the memory will still be reclaimed when the query is
+ * destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_threads_t object. (For consistency, we do provide a
+ * notmuch_threads_destroy function, but there's no good reason
+ * to call it if the query is about to be destroyed).
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ */
+func (self *Query) SearchThreads() *Threads {
+       threads := C.notmuch_query_search_threads(self.query)
+       if threads == nil {
+               return nil
+       }
+       return &Threads{threads: threads}
+}
+
+/* Execute a query for messages, returning a notmuch_messages_t object
+ * which can be used to iterate over the results. The returned
+ * messages object is owned by the query and as such, will only be
+ * valid until notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_messages_t *messages;
+ *     notmuch_message_t *message;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (messages = notmuch_query_search_messages (query);
+ *          notmuch_messages_valid (messages);
+ *          notmuch_messages_move_to_next (messages))
+ *     {
+ *         message = notmuch_messages_get (messages);
+ *         ....
+ *         notmuch_message_destroy (message);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a message before its containing
+ * query, you can call notmuch_message_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your message
+ * objects are long-lived, then you don't need to call
+ * notmuch_message_destroy and all the memory will still be reclaimed
+ * when the query is destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_messages_t object. (For consistency, we do provide a
+ * notmuch_messages_destroy function, but there's no good
+ * reason to call it if the query is about to be destroyed).
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ */
+func (self *Query) SearchMessages() *Messages {
+       msgs := C.notmuch_query_search_messages(self.query)
+       if msgs == nil {
+               return nil
+       }
+       return &Messages{messages: msgs}
+}
+
+/* Destroy a notmuch_query_t along with any associated resources.
+ *
+ * This will in turn destroy any notmuch_threads_t and
+ * notmuch_messages_t objects generated by this query, (and in
+ * turn any notmuch_thread_t and notmuch_message_t objects generated
+ * from those results, etc.), if such objects haven't already been
+ * destroyed.
+ */
+func (self *Query) Destroy() {
+       if self.query != nil {
+               C.notmuch_query_destroy(self.query)
+       }
+}
+
+/* Return an estimate of the number of messages matching a search
+ *
+ * This function performs a search and returns Xapian's best
+ * guess as to number of matching messages.
+ *
+ * If a Xapian exception occurs, this function may return 0 (after
+ * printing a message).
+ */
+func (self *Query) CountMessages() uint {
+       return uint(C.notmuch_query_count_messages(self.query))
+}
+
+/* Is the given 'threads' iterator pointing at a valid thread.
+ *
+ * When this function returns TRUE, notmuch_threads_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * 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) Valid() bool {
+       if self.threads == nil {
+               return false
+       }
+       valid := C.notmuch_threads_valid(self.threads)
+       if valid == 0 {
+               return false
+       }
+       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 &Thread{thread}
+}
+
+/* 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
+ * the notmuch_threads_t object will be reclaimed when the
+ * containg query object is destroyed.
+ */
+func (self *Threads) Destroy() {
+       if self.threads != nil {
+               C.notmuch_threads_destroy(self.threads)
+       }
+}
+
+/**
+ * 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' in oldest-first order.
+ *
+ * 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.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
+ */
+func (self *Thread) GetToplevelMessages() (*Messages, Status) {
+       if self.thread == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+
+       msgs := C.notmuch_thread_get_toplevel_messages(self.thread)
+       if msgs == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+       return &Messages{msgs}, STATUS_SUCCESS
+}
+
+/**
+ * Get a notmuch_thread_t iterator for all messages in 'thread' in
+ * oldest-first order.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
+ */
+func (self *Thread) GetMessages() (*Messages, Status) {
+       if self.thread == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+
+       msgs := C.notmuch_thread_get_messages(self.thread)
+       if msgs == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+       return &Messages{msgs}, STATUS_SUCCESS
+}
+
+/**
+ * Get the number of messages in 'thread' that matched the search.
+ *
+ * This count includes only the messages in this thread that were
+ * matched by the search from which the thread was created and were
+ * not excluded by any exclude tags passed in with the query (see
+ * notmuch_query_add_tag_exclude). Contrast with
+ * notmuch_thread_get_total_messages() .
+ */
+func (self *Thread) GetMatchedMessages() int {
+       if self.thread == nil {
+               return 0
+       }
+       return int(C.notmuch_thread_get_matched_messages(self.thread))
+}
+
+/**
+ * Get the authors of 'thread' as a UTF-8 string.
+ *
+ * The returned string is a comma-separated list of the names of the
+ * authors of mail messages in the query results that belong to this
+ * thread.
+ *
+ * The string contains authors of messages matching the query first, then
+ * non-matched authors (with the two groups separated by '|'). Within
+ * each group, authors are ordered by date.
+ *
+ * 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) GetAuthors() string {
+       if self.thread == nil {
+               return ""
+       }
+       str := C.notmuch_thread_get_authors(self.thread)
+       if str == nil {
+               return ""
+       }
+       return C.GoString(str)
+}
+
+/**
+ * Get the subject of 'thread' as a UTF-8 string.
+ *
+ * The subject is taken from the first message (according to the query
+ * order---see notmuch_query_set_sort) in the query results that
+ * belongs to this 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) GetSubject() string {
+       if self.thread == nil {
+               return ""
+       }
+       str := C.notmuch_thread_get_subject(self.thread)
+       if str == nil {
+               return ""
+       }
+       return C.GoString(str)
+}
+
+/**
+ * Get the date of the oldest message in 'thread' as a time_t value.
+ */
+func (self *Thread) GetOldestDate() int64 {
+       if self.thread == nil {
+               return 0
+       }
+       date := C.notmuch_thread_get_oldest_date(self.thread)
+
+       return int64(date)
+}
+
+/**
+ * Get the date of the newest message in 'thread' as a time_t value.
+ */
+func (self *Thread) GetNewestDate() int64 {
+       if self.thread == nil {
+               return 0
+       }
+       date := C.notmuch_thread_get_newest_date(self.thread)
+
+       return int64(date)
+}
+
+/**
+ * Get the tags for 'thread', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * Note: In the Notmuch database, tags are stored on individual
+ * messages, not on threads. So the tags returned here will be all
+ * tags of the messages which matched the search and which belong to
+ * this thread.
+ *
+ * The tags object is owned by the thread and as such, will only be
+ * valid for as long as the thread is valid, (for example, until
+ * notmuch_thread_destroy or until the query from which it derived is
+ * destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_thread_t *thread;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     thread = notmuch_threads_get (threads);
+ *
+ *     for (tags = notmuch_thread_get_tags (thread);
+ *          notmuch_tags_valid (tags);
+ *          notmuch_tags_move_to_next (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_thread_destroy (thread);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+func (self *Thread) GetTags() *Tags {
+       if self.thread == nil {
+               return nil
+       }
+
+       tags := C.notmuch_thread_get_tags(self.thread)
+       if tags == nil {
+               return nil
+       }
+
+       return &Tags{tags}
+}
+
+/**
+ * Destroy a notmuch_thread_t object.
+ */
+func (self *Thread) Destroy() {
+       if self.thread != nil {
+               C.notmuch_thread_destroy(self.thread)
+       }
+}
+
+/* Is the given 'messages' iterator pointing at a valid message.
+ *
+ * When this function returns TRUE, notmuch_messages_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * notmuch_messages_get will return NULL.
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+func (self *Messages) Valid() bool {
+       if self.messages == nil {
+               return false
+       }
+       valid := C.notmuch_messages_valid(self.messages)
+       if valid == 0 {
+               return false
+       }
+       return true
+}
+
+/* Get the current message from 'messages' as a notmuch_message_t.
+ *
+ * Note: The returned message belongs to 'messages' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+func (self *Messages) Get() *Message {
+       if self.messages == nil {
+               return nil
+       }
+       msg := C.notmuch_messages_get(self.messages)
+       if msg == nil {
+               return nil
+       }
+       return &Message{message: msg}
+}
+
+/* Move the 'messages' iterator to the next message.
+ *
+ * If 'messages' is already pointing at the last message then the
+ * iterator will be moved to a point just beyond that last message,
+ * (where notmuch_messages_valid will return FALSE and
+ * notmuch_messages_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+func (self *Messages) MoveToNext() {
+       if self.messages == nil {
+               return
+       }
+       C.notmuch_messages_move_to_next(self.messages)
+}
+
+/* Destroy a notmuch_messages_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_messages_t object will be reclaimed when the containing
+ * query object is destroyed.
+ */
+func (self *Messages) Destroy() {
+       if self.messages != nil {
+               C.notmuch_messages_destroy(self.messages)
+       }
+}
+
+/* Return a list of tags from all messages.
+ *
+ * The resulting list is guaranteed not to contain duplicated tags.
+ *
+ * WARNING: You can no longer iterate over messages after calling this
+ * function, because the iterator will point at the end of the list.
+ * We do not have a function to reset the iterator yet and the only
+ * way how you can iterate over the list again is to recreate the
+ * message list.
+ *
+ * The function returns NULL on error.
+ */
+func (self *Messages) CollectTags() *Tags {
+       if self.messages == nil {
+               return nil
+       }
+       tags := C.notmuch_messages_collect_tags(self.messages)
+       if tags == nil {
+               return nil
+       }
+       return &Tags{tags: tags}
+}
+
+/* Get the message ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (which is until the query from which it derived
+ * is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message has a unique message ID, (Notmuch will generate an ID for a
+ * message if the original file does not contain one).
+ */
+func (self *Message) GetMessageId() string {
+
+       if self.message == nil {
+               return ""
+       }
+       id := C.notmuch_message_get_message_id(self.message)
+       // we dont own id
+       // defer C.free(unsafe.Pointer(id))
+       if id == nil {
+               return ""
+       }
+       return C.GoString(id)
+}
+
+/* Get the thread ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (for example, until the user calls
+ * notmuch_message_destroy on 'message' or until a query from which it
+ * derived is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message belongs to a single thread.
+ */
+func (self *Message) GetThreadId() string {
+
+       if self.message == nil {
+               return ""
+       }
+       id := C.notmuch_message_get_thread_id(self.message)
+       // we dont own id
+       // defer C.free(unsafe.Pointer(id))
+
+       if id == nil {
+               return ""
+       }
+
+       return C.GoString(id)
+}
+
+/* Get a notmuch_messages_t iterator for all of the replies to
+ * 'message'.
+ *
+ * Note: This call only makes sense if 'message' was ultimately
+ * obtained from a notmuch_thread_t object, (such as by coming
+ * directly from the result of calling notmuch_thread_get_
+ * toplevel_messages or by any number of subsequent
+ * calls to notmuch_message_get_replies).
+ *
+ * If 'message' was obtained through some non-thread means, (such as
+ * by a call to notmuch_query_search_messages), then this function
+ * will return NULL.
+ *
+ * If there are no replies to 'message', this function will return
+ * NULL. (Note that notmuch_messages_valid will accept that NULL
+ * value as legitimate, and simply return FALSE for it.)
+ */
+func (self *Message) GetReplies() *Messages {
+       if self.message == nil {
+               return nil
+       }
+       msgs := C.notmuch_message_get_replies(self.message)
+       if msgs == nil {
+               return nil
+       }
+       return &Messages{messages: msgs}
+}
+
+/* Get a filename for the email corresponding to 'message'.
+ *
+ * The returned filename is an absolute filename, (the initial
+ * component will match notmuch_database_get_path() ).
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Note: If this message corresponds to multiple files in the mail
+ * store, (that is, multiple files contain identical message IDs),
+ * this function will arbitrarily return a single one of those
+ * filenames.
+ */
+func (self *Message) GetFileName() string {
+       if self.message == nil {
+               return ""
+       }
+       fname := C.notmuch_message_get_filename(self.message)
+       // we dont own fname
+       // defer C.free(unsafe.Pointer(fname))
+
+       if fname == nil {
+               return ""
+       }
+
+       return C.GoString(fname)
+}
+
+type Flag C.notmuch_message_flag_t
+
+const (
+       MESSAGE_FLAG_MATCH Flag = 0
+)
+
+/* Get a value of a flag for the email corresponding to 'message'. */
+func (self *Message) GetFlag(flag Flag) bool {
+       if self.message == nil {
+               return false
+       }
+       v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag))
+       if v == 0 {
+               return false
+       }
+       return true
+}
+
+/* Set a value of a flag for the email corresponding to 'message'. */
+func (self *Message) SetFlag(flag Flag, value bool) {
+       if self.message == nil {
+               return
+       }
+       var v C.notmuch_bool_t = 0
+       if value {
+               v = 1
+       }
+       C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v)
+}
+
+/* Get the timestamp (seconds since the epoch) of 'message'.
+ *
+ * Return status:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Timestamp successfully retrieved
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'message' argument is NULL
+ *
+ */
+func (self *Message) GetDate() (int64, Status) {
+       if self.message == nil {
+               return -1, STATUS_NULL_POINTER
+       }
+       timestamp := C.notmuch_message_get_date(self.message)
+       return int64(timestamp), STATUS_SUCCESS
+}
+
+/* Get the value of the specified header from 'message'.
+ *
+ * The value will be read from the actual message file, not from the
+ * notmuch database. The header name is case insensitive.
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Returns an empty string ("") if the message does not contain a
+ * header line matching 'header'. Returns NULL if any error occurs.
+ */
+func (self *Message) GetHeader(header string) string {
+       if self.message == nil {
+               return ""
+       }
+
+       var c_header *C.char = C.CString(header)
+       defer C.free(unsafe.Pointer(c_header))
+
+       /* we dont own value */
+       value := C.notmuch_message_get_header(self.message, c_header)
+       if value == nil {
+               return ""
+       }
+
+       return C.GoString(value)
+}
+
+/* Get the tags for 'message', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * The tags object is owned by the message and as such, will only be
+ * valid for as long as the message is valid, (which is until the
+ * query from which it derived is destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_message_t *message;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     message = notmuch_database_find_message (database, message_id);
+ *
+ *     for (tags = notmuch_message_get_tags (message);
+ *          notmuch_tags_valid (tags);
+ *          notmuch_result_move_to_next (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_message_destroy (message);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+func (self *Message) GetTags() *Tags {
+       if self.message == nil {
+               return nil
+       }
+       tags := C.notmuch_message_get_tags(self.message)
+       if tags == nil {
+               return nil
+       }
+       return &Tags{tags: tags}
+}
+
+/* The longest possible tag value. */
+const TAG_MAX = 200
+
+/* Add a tag to the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) AddTag(tag string) Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       c_tag := C.CString(tag)
+       defer C.free(unsafe.Pointer(c_tag))
+
+       return Status(C.notmuch_message_add_tag(self.message, c_tag))
+}
+
+/* Remove a tag from the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) RemoveTag(tag string) Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       c_tag := C.CString(tag)
+       defer C.free(unsafe.Pointer(c_tag))
+
+       return Status(C.notmuch_message_remove_tag(self.message, c_tag))
+}
+
+/* Remove all tags from the given message.
+ *
+ * See notmuch_message_freeze for an example showing how to safely
+ * replace tag values.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) RemoveAllTags() Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       return Status(C.notmuch_message_remove_all_tags(self.message))
+}
+
+/* Freeze the current state of 'message' within the database.
+ *
+ * This means that changes to the message state, (via
+ * notmuch_message_add_tag, notmuch_message_remove_tag, and
+ * notmuch_message_remove_all_tags), will not be committed to the
+ * database until the message is thawed with notmuch_message_thaw.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls will
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * The ability to do freeze/thaw allows for safe transactions to
+ * change tag values. For example, explicitly setting a message to
+ * have a given set of tags might look like this:
+ *
+ *    notmuch_message_freeze (message);
+ *
+ *    notmuch_message_remove_all_tags (message);
+ *
+ *    for (i = 0; i < NUM_TAGS; i++)
+ *        notmuch_message_add_tag (message, tags[i]);
+ *
+ *    notmuch_message_thaw (message);
+ *
+ * With freeze/thaw used like this, the message in the database is
+ * guaranteed to have either the full set of original tag values, or
+ * the full set of new tag values, but nothing in between.
+ *
+ * Imagine the example above without freeze/thaw and the operation
+ * somehow getting interrupted. This could result in the message being
+ * left with no tags if the interruption happened after
+ * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully frozen.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) Freeze() Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       return Status(C.notmuch_message_freeze(self.message))
+}
+
+/* Thaw the current 'message', synchronizing any changes that may have
+ * occurred while 'message' was frozen into the notmuch database.
+ *
+ * See notmuch_message_freeze for an example of how to use this
+ * function to safely provide tag changes.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls with
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least
+ *     its frozen count has successfully been reduced by 1).
+ *
+ * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw
+ *     an unfrozen message. That is, there have been an unbalanced
+ *     number of calls to notmuch_message_freeze and
+ *     notmuch_message_thaw.
+ */
+func (self *Message) Thaw() Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+
+       return Status(C.notmuch_message_thaw(self.message))
+}
+
+/* Destroy a notmuch_message_t object.
+ *
+ * It can be useful to call this function in the case of a single
+ * query object with many messages in the result, (such as iterating
+ * over the entire database). Otherwise, it's fine to never call this
+ * function and there will still be no memory leaks. (The memory from
+ * the messages get reclaimed when the containing query is destroyed.)
+ */
+func (self *Message) Destroy() {
+       if self.message == nil {
+               return
+       }
+       C.notmuch_message_destroy(self.message)
+}
+
+/* Is the given 'tags' iterator pointing at a valid tag.
+ *
+ * When this function returns TRUE, notmuch_tags_get will return a
+ * valid string. Whereas when this function returns FALSE,
+ * notmuch_tags_get will return NULL.
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+func (self *Tags) Valid() bool {
+       if self.tags == nil {
+               return false
+       }
+       v := C.notmuch_tags_valid(self.tags)
+       if v == 0 {
+               return false
+       }
+       return true
+}
+
+/* Get the current tag from 'tags' as a string.
+ *
+ * Note: The returned string belongs to 'tags' and has a lifetime
+ * identical to it (and the query to which it ultimately belongs).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+func (self *Tags) Get() string {
+       if self.tags == nil {
+               return ""
+       }
+       s := C.notmuch_tags_get(self.tags)
+       // we dont own 's'
+
+       return C.GoString(s)
+}
+func (self *Tags) String() string {
+       return self.Get()
+}
+
+/* Move the 'tags' iterator to the next tag.
+ *
+ * If 'tags' is already pointing at the last tag then the iterator
+ * will be moved to a point just beyond that last tag, (where
+ * notmuch_tags_valid will return FALSE and notmuch_tags_get will
+ * return NULL).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+func (self *Tags) MoveToNext() {
+       if self.tags == nil {
+               return
+       }
+       C.notmuch_tags_move_to_next(self.tags)
+}
+
+/* Destroy a notmuch_tags_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_tags_t object will be reclaimed when the containing
+ * message or query objects are destroyed.
+ */
+func (self *Tags) Destroy() {
+       if self.tags == nil {
+               return
+       }
+       C.notmuch_tags_destroy(self.tags)
+}
+
+// TODO: wrap notmuch_directory_<fct>
+
+/* Destroy a notmuch_directory_t object. */
+func (self *Directory) Destroy() {
+       if self.dir == nil {
+               return
+       }
+       C.notmuch_directory_destroy(self.dir)
+}
+
+// TODO: wrap notmuch_filenames_<fct>
+
+/* Destroy a notmuch_filenames_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_filenames_t object will be reclaimed when the
+ * containing directory object is destroyed.
+ *
+ * It is acceptable to pass NULL for 'filenames', in which case this
+ * function will do nothing.
+ */
+func (self *Filenames) Destroy() {
+       if self.fnames == nil {
+               return
+       }
+       C.notmuch_filenames_destroy(self.fnames)
+}
+
+/* EOF */
index 9c3379eb0015121b26a8d38684bfbc1304227da5..26996c4ab2ccd1136b56e71e0c9d8c86c21a295a 100644 (file)
@@ -24,8 +24,8 @@ for a --output=symlinks flag to notmuch.
 
 
 [1]: http://www.mutt.org/
-[2]: http://notmuchmail.org/
-[3]: http://upsilon.cc/~zack/blog/posts/2011/01/how_to_use_Notmuch_with_Mutt/
+[2]: https://notmuchmail.org/
+[3]: https://upsilon.cc/~zack/blog/posts/2011/01/how_to_use_Notmuch_with_Mutt/
 
 
 Requirements
@@ -57,5 +57,5 @@ notmuch-mutt is copyright (C) 2011-2012 Stefano Zacchiroli <zack@upsilon.cc>.
 
 notmuch-mutt is released under the terms of the GNU General Public License
 (GPL), version 3 or above. A copy of the license is available online at
-<http://www.gnu.org/licenses/>.
+<https://www.gnu.org/licenses/>.
 
index 97fb2d85160cb9a495de68c31103671beeae3fc0..0e46a8c1b95e76163eed68694aa5a1a973c8b371 100755 (executable)
@@ -91,7 +91,7 @@ sub get_message_id() {
        $mid = $1;
     } else {  # Message-ID header not found, synthesize a message id
              # based on SHA1, as notmuch would do.  See:
-             # http://git.notmuchmail.org/git/notmuch/blob/HEAD:/lib/sha1.c
+             # https://git.notmuchmail.org/git/notmuch/blob/HEAD:/lib/sha1.c
        my $sha = Digest::SHA->new(1);
        $sha->add($_) foreach(@headers);
        $sha->addfile(\*STDIN);
diff --git a/contrib/notmuch-vim/Makefile b/contrib/notmuch-vim/Makefile
deleted file mode 100644 (file)
index f17bebf..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-.PHONY: all help install link symlink
-
-files = plugin/notmuch.vim \
-       $(wildcard syntax/notmuch-*.vim)
-prefix = $(HOME)/.vim
-destdir = $(prefix)/plugin
-
-INSTALL = install -D -m644
-
-all: help
-
-help:
-       @echo "I don't actually build anything, but I will help you install"
-       @echo "notmuch support for vim."
-       @echo
-       @echo "    make install     - copy plugin scripts and syntax files to ~/.vim"
-       @echo "    make symlink     - create symlinks in ~/.vim (useful for development)"
-
-install:
-       @for x in $(files); do $(INSTALL) $(CURDIR)/$$x $(prefix)/$$x; done
-
-link symlink: INSTALL = ln -fs
-link symlink: install
diff --git a/contrib/notmuch-vim/README b/contrib/notmuch-vim/README
deleted file mode 100644 (file)
index 53f1c4e..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-This directory contains a vim script that allows reading notmuch mail
-through vim.
-
-NOTE: this is a work in progress.  Patches welcome. <bart@jukie.net>
-
-Dependencies:
-    notmuch:
-        Naturally, it expects you have notmuch installed and configured.
-
-    sendmail:
-        To send mail, notmuch.vim uses sendmail as default.  Most modern MTAs
-        provide a compatibility binary, and so should work well.
-
-
-To install:
-        make install
-
-
-To run:
-        vim -c ':NotMuch'
-
-    from vim:
-        :NotMuch
-        :NotMuch new to:bart@jukie.net 'subject:this is a test'
-
-
-Buffer types:
-    [notmuch-folders]
-        Folder list, or technically a list of saved searches.
-
-        Keybindings:
-            <Enter> - show the selected search
-            m       - compose a new message
-            s       - enter search criteria
-            =       - refresh display
-
-    [notmuch-search]
-        You are presented with the search results when you run :NotMuch.
-
-        Keybindings:
-            <Space> - show the selected thread collapsing unmatched items
-            <Enter> - show the entire selected thread
-            a       - archive message (remove inbox tag)
-            f       - filter the current search terms
-            o       - toggle search screen order
-            m       - compose a new message
-            r       - reply to thread
-            s       - enter search criteria
-            ,s      - alter search criteria
-            t       - filter the current search terms with tags
-            q       - return to folder display, or undo filter
-            +       - add tag(s) to selected message
-            -       - remove tag(s) from selected message
-            =       - refresh display
-            ?       - reveal the thread ID of what's under cursor
-            ^]      - search using word under cursor
-
-    [notmuch-show]
-        This is the display of the message.
-
-        Keybindings:
-            <Space> - mark read, archive, go to next matching message
-            ^n      - next message
-            ^p      - previous message
-            b       - toggle folding of message bodies
-            c       - toggle folding of citations
-            h       - toggle folding of extra header lines
-            i       - toggle folding of signatures
-            m       - compose a new message
-            r       - reply to the message
-            s       - enter search criteria
-            q       - return to search display
-            ?       - reveal the message and thread IDs of what's under cursor
-            ^]      - search using word under cursor
-
-    [notmuch-compose]
-        When you're writing an email, you're in this mode.
-
-        Insert-mode keybindings:
-            <Tab>   - go to the next header line
-
-        Normal-mode keybindings:
-            <Tab>   - go to the next header line
-            ,s      - send this message
-            ,q      - abort this message
-
diff --git a/contrib/notmuch-vim/notmuch.yaml b/contrib/notmuch-vim/notmuch.yaml
deleted file mode 100644 (file)
index 3d8422c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-addon: notmuch
-description: "notmuch mail user interface"
-files:
-  - plugin/notmuch.vim
-  - syntax/notmuch-compose.vim
-  - syntax/notmuch-folders.vim
-  - syntax/notmuch-search.vim
-  - syntax/notmuch-show.vim
diff --git a/contrib/notmuch-vim/plugin/notmuch.vim b/contrib/notmuch-vim/plugin/notmuch.vim
deleted file mode 100644 (file)
index 8f27fb9..0000000
+++ /dev/null
@@ -1,1432 +0,0 @@
-" notmuch.vim plugin --- run notmuch within vim
-"
-" Copyright Â© Carl Worth
-"
-" This file is part of Notmuch.
-"
-" Notmuch is free software: you can redistribute it and/or modify it
-" under the terms of the GNU General Public License as published by
-" the Free Software Foundation, either version 3 of the License, or
-" (at your option) any later version.
-"
-" Notmuch is distributed in the hope that it will be useful, but
-" WITHOUT ANY WARRANTY; without even the implied warranty of
-" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-" General Public License for more details.
-"
-" You should have received a copy of the GNU General Public License
-" along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
-"
-" Authors: Bart Trojanowski <bart@jukie.net>
-" Contributors: Felipe Contreras <felipe.contreras@gmail.com>,
-"   Peter Hartman <peterjohnhartman@gmail.com>
-"
-" --- configuration defaults {{{1
-
-let s:notmuch_defaults = {
-        \ 'g:notmuch_cmd':                           'notmuch'                    ,
-        \ 'g:notmuch_sendmail':                      '/usr/sbin/sendmail'         ,
-        \ 'g:notmuch_debug':                         0                            ,
-        \
-        \ 'g:notmuch_search_newest_first':           1                            ,
-        \ 'g:notmuch_search_from_column_width':      20                           ,
-        \
-        \ 'g:notmuch_show_fold_signatures':          1                            ,
-        \ 'g:notmuch_show_fold_citations':           1                            ,
-        \ 'g:notmuch_show_fold_bodies':              0                            ,
-        \ 'g:notmuch_show_fold_headers':             1                            ,
-        \
-        \ 'g:notmuch_show_message_begin_regexp':     '\fmessage{'                ,
-        \ 'g:notmuch_show_message_end_regexp':       '\fmessage}'                ,
-        \ 'g:notmuch_show_header_begin_regexp':      '\fheader{'                 ,
-        \ 'g:notmuch_show_header_end_regexp':        '\fheader}'                 ,
-        \ 'g:notmuch_show_body_begin_regexp':        '\fbody{'                   ,
-        \ 'g:notmuch_show_body_end_regexp':          '\fbody}'                   ,
-        \ 'g:notmuch_show_attachment_begin_regexp':  '\fattachment{'             ,
-        \ 'g:notmuch_show_attachment_end_regexp':    '\fattachment}'             ,
-        \ 'g:notmuch_show_part_begin_regexp':        '\fpart{'                   ,
-        \ 'g:notmuch_show_part_end_regexp':          '\fpart}'                   ,
-        \ 'g:notmuch_show_marker_regexp':            '\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$',
-        \
-        \ 'g:notmuch_show_message_parse_regexp':     '\(id:[^ ]*\) depth:\([0-9]*\) match:\([0-9]*\) excluded:\([0-9]*\) filename:\(.*\)$',
-        \ 'g:notmuch_show_tags_regexp':              '(\([^)]*\))$'               ,
-        \
-        \ 'g:notmuch_show_signature_regexp':         '^\(-- \?\|_\+\)$'           ,
-        \ 'g:notmuch_show_signature_lines_max':      12                           ,
-        \
-        \ 'g:notmuch_show_citation_regexp':          '^\s*>'                      ,
-        \
-        \ 'g:notmuch_compose_insert_mode_start':     1                            ,
-        \ 'g:notmuch_compose_header_help':           1                            ,
-        \ 'g:notmuch_compose_temp_file_dir':         '~/.notmuch/compose'         ,
-        \ }
-
-" defaults for g:notmuch_initial_search_words
-" override with: let g:notmuch_initial_search_words = [ ... ]
-let s:notmuch_initial_search_words_defaults = [
-        \ 'tag:inbox and tag:unread',
-        \ ]
-
-" defaults for g:notmuch_show_headers
-" override with: let g:notmuch_show_headers = [ ... ]
-let s:notmuch_show_headers_defaults = [
-        \ 'Subject',
-        \ 'To',
-        \ 'Cc',
-        \ 'Bcc',
-        \ 'Date',
-        \ ]
-
-" defaults for g:notmuch_folders
-" override with: let g:notmuch_folders = [ ... ]
-let s:notmuch_folders_defaults = [
-        \ [ 'new',    'tag:inbox and tag:unread' ],
-        \ [ 'inbox',  'tag:inbox'                ],
-        \ [ 'unread', 'tag:unread'               ],
-        \ ]
-
-" defaults for g:notmuch_signature
-" override with: let g:notmuch_signature = [ ... ]
-let s:notmuch_signature_defaults = [
-        \ '',
-        \ '-- ',
-        \ 'email sent from notmuch.vim plugin'
-        \ ]
-
-" defaults for g:notmuch_compose_headers
-" override with: let g:notmuch_compose_headers = [ ... ]
-let s:notmuch_compose_headers_defaults = [
-        \ 'From',
-        \ 'To',
-        \ 'Cc',
-        \ 'Bcc',
-        \ 'Subject'
-        \ ]
-
-" --- keyboard mapping definitions {{{1
-
-" --- --- bindings for folders mode {{{2
-
-let g:notmuch_folders_maps = {
-        \ 'm':          ':call <SID>NM_new_mail()<CR>',
-        \ 's':          ':call <SID>NM_search_prompt()<CR>',
-        \ 'q':          ':call <SID>NM_kill_this_buffer()<CR>',
-        \ '=':          ':call <SID>NM_folders_refresh_view()<CR>',
-        \ '<Enter>':    ':call <SID>NM_folders_show_search()<CR>',
-        \ }
-
-" --- --- bindings for search screen {{{2
-let g:notmuch_search_maps = {
-        \ '<Space>':    ':call <SID>NM_search_show_thread(0)<CR>',
-        \ '<Enter>':    ':call <SID>NM_search_show_thread(1)<CR>',
-        \ '<C-]>':      ':call <SID>NM_search_expand(''<cword>'')<CR>',
-        \ 'I':          ':call <SID>NM_search_mark_read_thread()<CR>',
-        \ 'a':          ':call <SID>NM_search_archive_thread()<CR>',
-        \ 'A':          ':call <SID>NM_search_mark_read_then_archive_thread()<CR>',
-        \ 'D':          ':call <SID>NM_search_delete_thread()<CR>',
-        \ 'f':          ':call <SID>NM_search_filter()<CR>',
-        \ 'm':          ':call <SID>NM_new_mail()<CR>',
-        \ 'o':          ':call <SID>NM_search_toggle_order()<CR>',
-        \ 'r':          ':call <SID>NM_search_reply_to_thread()<CR>',
-        \ 's':          ':call <SID>NM_search_prompt()<CR>',
-        \ ',s':         ':call <SID>NM_search_edit()<CR>',
-        \ 't':          ':call <SID>NM_search_filter_by_tag()<CR>',
-        \ 'q':          ':call <SID>NM_kill_this_buffer()<CR>',
-        \ '+':          ':call <SID>NM_search_add_tags([])<CR>',
-        \ '-':          ':call <SID>NM_search_remove_tags([])<CR>',
-        \ '=':          ':call <SID>NM_search_refresh_view()<CR>',
-        \ '?':          ':echo <SID>NM_search_thread_id() . ''  @ '' . join(<SID>NM_get_search_words())<CR>',
-        \ }
-
-" --- --- bindings for show screen {{{2
-let g:notmuch_show_maps = {
-        \ '<C-P>':      ':call <SID>NM_show_previous(1, 0)<CR>',
-        \ '<C-N>':      ':call <SID>NM_show_next(1, 0)<CR>',
-        \ '<C-]>':      ':call <SID>NM_search_expand(''<cword>'')<CR>',
-        \ 'q':          ':call <SID>NM_kill_this_buffer()<CR>',
-        \ 's':          ':call <SID>NM_search_prompt()<CR>',
-        \
-        \ 'b':          ':call <SID>NM_show_fold_toggle(''b'', ''bdy'', !g:notmuch_show_fold_bodies)<CR>',
-        \ 'c':          ':call <SID>NM_show_fold_toggle(''c'', ''cit'', !g:notmuch_show_fold_citations)<CR>',
-        \ 'h':          ':call <SID>NM_show_fold_toggle(''h'', ''hdr'', !g:notmuch_show_fold_headers)<CR>',
-        \ 'i':          ':call <SID>NM_show_fold_toggle(''i'', ''sig'', !g:notmuch_show_fold_signatures)<CR>',
-        \
-        \ 'I':          ':call <SID>NM_show_mark_read_thread()<CR>',
-        \ 'a':          ':call <SID>NM_show_archive_thread()<CR>',
-        \ 'A':          ':call <SID>NM_show_mark_read_then_archive_thread()<CR>',
-        \ 'D':          ':call <SID>NM_show_delete_thread()<CR>',
-        \ 'd':          ':call <SID>NM_show_delete_message()<CR>',
-        \ 'N':          ':call <SID>NM_show_mark_read_then_next_open_message()<CR>',
-        \ 'v':          ':call <SID>NM_show_view_all_mime_parts()<CR>',
-        \ '+':          ':call <SID>NM_show_add_tag()<CR>',
-        \ '-':          ':call <SID>NM_show_remove_tag()<CR>',
-        \ '<Space>':    ':call <SID>NM_show_advance_marking_read_and_archiving()<CR>',
-        \ '\|':         ':call <SID>NM_show_pipe_message()<CR>',
-        \
-        \ '<S-Tab>':    ':call <SID>NM_show_previous_fold()<CR>',
-        \ '<Tab>':      ':call <SID>NM_show_next_fold()<CR>',
-        \ '<Enter>':    ':call <SID>NM_show_toggle_fold()<CR>',
-        \
-        \ 'r':          ':call <SID>NM_show_reply()<CR>',
-        \ 'm':          ':call <SID>NM_new_mail()<CR>',
-        \ '?':          ':echo <SID>NM_show_message_id() . ''  @ '' . join(<SID>NM_get_search_words())<CR>',
-        \ }
-
-" --- --- bindings for compose screen {{{2
-let g:notmuch_compose_nmaps = {
-        \ ',s':         ':call <SID>NM_compose_send()<CR>',
-        \ ',a':         ':call <SID>NM_compose_attach()<CR>',
-        \ ',q':         ':call <SID>NM_kill_this_buffer()<CR>',
-        \ '<Tab>':      ':call <SID>NM_compose_next_entry_area()<CR>',
-        \ }
-let g:notmuch_compose_imaps = {
-        \ '<Tab>':      '<C-r>=<SID>NM_compose_next_entry_area()<CR>',
-        \ }
-
-" --- implement folders screen {{{1
-
-function! s:NM_cmd_folders(words)
-        if len(a:words)
-                throw 'Not expecting any arguments for folders command.'
-        endif
-        let cmd = ['count']
-        let disp = []
-        let searches = []
-        for entry in g:notmuch_folders
-                let [ name, search ] = entry
-                let data = s:NM_run(cmd + [search])
-                let cnt = matchlist(data, '\(\d\+\)')[1]
-                call add(disp, printf('%9d %-20s (%s)', cnt, name, search))
-                call add(searches, search)
-        endfor
-
-        call <SID>NM_newBuffer('', 'folders', join(disp, "\n"))
-        let b:nm_searches = searches
-        let b:nm_timestamp = reltime()
-
-        call <SID>NM_cmd_folders_mksyntax()
-        call <SID>NM_set_map('n', g:notmuch_folders_maps)
-        setlocal cursorline
-        setlocal nowrap
-endfunction
-
-function! s:NM_cmd_folders_mksyntax()
-endfunction
-
-" --- --- folders screen action functions {{{2
-
-function! s:NM_folders_refresh_view()
-        let lno = line('.')
-        setlocal bufhidden=delete
-        call s:NM_cmd_folders([])
-        exec printf('norm %dG', lno)
-endfunction
-
-function! s:NM_folders_show_search()
-        let line = line('.')
-        let search = b:nm_searches[line-1]
-
-        let prev_bufnr = bufnr('%')
-        setlocal bufhidden=hide
-        call <SID>NM_cmd_search([search])
-        setlocal bufhidden=delete
-        let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-
-" --- implement search screen {{{1
-
-function! s:NM_cmd_search(words)
-        let cmd = ['search']
-        if g:notmuch_search_newest_first
-                let cmd = cmd + ['--sort=newest-first']
-        else
-                let cmd = cmd + ['--sort=oldest-first']
-        endif
-        let data = s:NM_run(cmd + a:words)
-        let lines = split(data, "\n")
-        let disp = copy(lines)
-        call map(disp, 's:NM_cmd_search_fmtline(v:val)')
-
-        call <SID>NM_newBuffer('', 'search', join(disp, "\n"))
-        let b:nm_raw_lines = lines
-        let b:nm_search_words = a:words
-
-        call <SID>NM_set_map('n', g:notmuch_search_maps)
-        setlocal cursorline
-        setlocal nowrap
-endfunction
-function! s:NM_cmd_search_fmtline(line)
-        let m = matchlist(a:line, '^\(thread:\S\+\)\s\(.\{12\}\) \[\(\d\+\)/\d\+\] \([^;]\+\); \%(\[[^\[]\+\] \)*\(.*\) (\([^(]*\))$')
-        if !len(m)
-                return 'ERROR PARSING: ' . a:line
-        endif
-        let max = g:notmuch_search_from_column_width
-        let flist = {}
-        for at in split(m[4], '[|,] ')
-                let p = split(at, '[@.]')
-                let flist[p[0]] = 1
-        endfor
-        let from = join(keys(flist), ", ")
-        return printf("%-12s %3s %-20.20s | %s (%s)", m[2], m[3], from, m[5], m[6])
-endfunction
-
-" --- --- search screen action functions {{{2
-
-function! s:NM_search_show_thread(everything)
-        let words = [ <SID>NM_search_thread_id() ]
-        if !a:everything && exists('b:nm_search_words')
-                call extend(words, ['AND', '('])
-                call extend(words, b:nm_search_words)
-                call add(words, ')')
-        endif
-        call <SID>NM_cmd_show(words)
-        let b:nm_show_everything = a:everything
-endfunction
-
-function! s:NM_search_prompt()
-        " TODO: input() can support completion
-        let text = input('NotMuch Search: ')
-        if strlen(text)
-                let tags = split(text)
-        else
-                let tags = s:notmuch_initial_search_words_defaults
-        endif
-        let prev_bufnr = bufnr('%')
-        if b:nm_type == 'search' && exists('b:nm_prev_bufnr')
-                " TODO: we intend to replace the current buffer,
-                "       ... maybe we could just clear it
-                let prev_bufnr = b:nm_prev_bufnr
-                setlocal bufhidden=delete
-        else
-                setlocal bufhidden=hide
-        endif
-        call <SID>NM_cmd_search(tags)
-        setlocal bufhidden=delete
-        let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-function! s:NM_search_edit()
-        " TODO: input() can support completion
-        let text = input('NotMuch Search: ', join(b:nm_search_words, ' '))
-        if strlen(text)
-                call <SID>NM_cmd_search(split(text))
-        endif
-endfunction
-
-function! s:NM_search_mark_read_thread()
-        call <SID>NM_tag([], ['-unread'])
-        norm j
-endfunction
-
-function! s:NM_search_archive_thread()
-        call <SID>NM_tag([], ['-inbox'])
-        norm j
-endfunction
-
-function! s:NM_search_mark_read_then_archive_thread()
-        call <SID>NM_tag([], ['-unread', '-inbox'])
-        norm j
-endfunction
-
-function! s:NM_search_delete_thread()
-        call <SID>NM_tag([], ['+delete','-inbox','-unread'])
-        norm j
-endfunction
-
-function! s:NM_search_filter()
-        call <SID>NM_search_filter_helper('Filter: ', '', '')
-endfunction
-
-function! s:NM_search_filter_by_tag()
-        call <SID>NM_search_filter_helper('Filter Tag(s): ', 'tag:', 'and')
-endfunction
-
-function! s:NM_search_filter_helper(prompt, prefix, joiner)
-        " TODO: input() can support completion
-        let text = substitute(input(a:prompt), '\v(^\s*|\s*$|\n)', '', 'g')
-        if !strlen(text)
-                return
-        endif
-
-        let tags = b:nm_search_words + ['AND']
-                 \ + <SID>NM_combine_tags(a:prefix, split(text), a:joiner, '()')
-
-        let prev_bufnr = bufnr('%')
-        setlocal bufhidden=hide
-        call <SID>NM_cmd_search(tags)
-        setlocal bufhidden=delete
-        let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-function! s:NM_search_toggle_order()
-        let g:notmuch_search_newest_first = !g:notmuch_search_newest_first
-        " FIXME: maybe this would be better done w/o reading re-reading the lines
-        "         reversing the b:nm_raw_lines and the buffer lines would be better
-        call <SID>NM_search_refresh_view()
-endfunction
-
-function! s:NM_search_reply_to_thread()
-        let cmd = ['reply']
-        call add(cmd, <SID>NM_search_thread_id())
-        call add(cmd, 'AND')
-        call extend(cmd, <SID>NM_get_search_words())
-
-        let data = <SID>NM_run(cmd)
-        let lines = split(data, "\n")
-        call <SID>NM_newComposeBuffer(lines, 0)
-endfunction
-
-function! s:NM_search_add_tags(tags)
-        call <SID>NM_search_add_remove_tags('Add Tag(s): ', '+', a:tags)
-endfunction
-
-function! s:NM_search_remove_tags(tags)
-        call <SID>NM_search_add_remove_tags('Remove Tag(s): ', '-', a:tags)
-endfunction
-
-function! s:NM_search_refresh_view()
-        let lno = line('.')
-        let prev_bufnr = b:nm_prev_bufnr
-        setlocal bufhidden=delete
-        call <SID>NM_cmd_search(b:nm_search_words)
-        let b:nm_prev_bufnr = prev_bufnr
-        " FIXME: should find the line of the thread we were on if possible
-        exec printf('norm %dG', lno)
-endfunction
-
-" --- --- search screen helper functions {{{2
-
-function! s:NM_search_thread_id()
-        if !exists('b:nm_raw_lines')
-                throw 'Eeek! no b:nm_raw_lines'
-        endif
-        let mnum = line('.') - 1
-        if len(b:nm_raw_lines) <= mnum
-                return ''
-        endif
-        let info = b:nm_raw_lines[mnum]
-        let what = split(info, '\s\+')[0]
-        return what
-endfunction
-
-function! s:NM_search_add_remove_tags(prompt, prefix, intags)
-        if type(a:intags) != type([]) || len(a:intags) == 0
-                " TODO: input() can support completion
-                let text = input(a:prompt)
-                if !strlen(text)
-                        return
-                endif
-                let tags = split(text, ' ')
-        else
-                let tags = a:intags
-        endif
-        call map(tags, 'a:prefix . v:val')
-        call <SID>NM_tag([], tags)
-endfunction
-
-" --- implement show screen {{{1
-
-function! s:NM_cmd_show(words)
-        let prev_bufnr = bufnr('%')
-        let data = s:NM_run(['show', '--entire-thread'] + a:words)
-        let lines = split(data, "\n")
-
-        let info = s:NM_cmd_show_parse(lines)
-
-        setlocal bufhidden=hide
-        call <SID>NM_newBuffer('', 'show', join(info['disp'], "\n"))
-        setlocal bufhidden=delete
-        let b:nm_search_words = a:words
-        let b:nm_raw_info = info
-        let b:nm_prev_bufnr = prev_bufnr
-
-        call <SID>NM_cmd_show_mkfolds()
-        call <SID>NM_cmd_show_mksyntax()
-        call <SID>NM_set_map('n', g:notmuch_show_maps)
-        setlocal foldtext=NM_cmd_show_foldtext()
-        setlocal fillchars=
-        setlocal foldcolumn=6
-
-endfunction
-
-function! s:NM_show_previous(can_change_thread, find_matching)
-        let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
-        let info = b:nm_raw_info
-        let lnum = line('.')
-        for msg in reverse(copy(info['msgs']))
-                if a:find_matching && msg['match'] == '0'
-                        continue
-                endif
-                if lnum <= msg['start']
-                        continue
-                endif
-
-                exec printf('norm %dGzt', msg['start'])
-                " TODO: try to fit the message on screen
-                return
-        endfor
-        if !a:can_change_thread
-                return
-        endif
-        call <SID>NM_kill_this_buffer()
-        if line('.') > 1
-                norm k
-                call <SID>NM_search_show_thread(everything)
-                norm G
-                call <SID>NM_show_previous(0, a:find_matching)
-        else
-                echo 'No more messages.'
-        endif
-endfunction
-
-function! s:NM_show_next(can_change_thread, find_matching)
-        let info = b:nm_raw_info
-        let lnum = line('.')
-        for msg in info['msgs']
-                if a:find_matching && msg['match'] == '0'
-                        continue
-                endif
-                if lnum >= msg['start']
-                        continue
-                endif
-
-                exec printf('norm %dGzt', msg['start'])
-                " TODO: try to fit the message on screen
-                return
-        endfor
-        if a:can_change_thread
-                call <SID>NM_show_next_thread()
-        endif
-endfunction
-
-function! s:NM_show_next_thread()
-        let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0
-        call <SID>NM_kill_this_buffer()
-        if line('.') != line('$')
-                norm j
-                call <SID>NM_search_show_thread(everything)
-        else
-                echo 'No more messages.'
-        endif
-endfunction
-
-function! s:NM_show_mark_read_thread()
-        call <SID>NM_tag(b:nm_search_words, ['-unread'])
-        call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_archive_thread()
-        call <SID>NM_tag(b:nm_search_words, ['-inbox'])
-        call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_mark_read_then_archive_thread()
-        call <SID>NM_tag(b:nm_search_words, ['-unread', '-inbox'])
-        call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_delete_thread()
-        call <SID>NM_tag(b:nm_search_words, ['+delete', '-inbox', '-unread'])
-        call <SID>NM_show_next_thread()
-endfunction
-
-function! s:NM_show_delete_message()
-        let msg = <SID>NM_show_get_message_for_line(line('.'))
-        call <SID>NM_tag([msg['id']], ['+delete', '-inbox', '-unread'])
-endfunction
-
-function! s:NM_show_mark_read_then_next_open_message()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_previous_message()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_reply()
-        let cmd = ['reply']
-        call add(cmd, <SID>NM_show_message_id())
-        call add(cmd, 'AND')
-        call extend(cmd, <SID>NM_get_search_words())
-
-        let data = <SID>NM_run(cmd)
-        let lines = split(data, "\n")
-        call <SID>NM_newComposeBuffer(lines, 0)
-endfunction
-
-function! s:NM_show_view_all_mime_parts()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_view_raw_message()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_add_tag()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_remove_tag()
-        echo 'not implemented'
-endfunction
-
-" if entire message is not visible scroll down 1/2 page or less to get to the bottom of message
-" otherwise go to next message
-" any message that is viewed entirely has inbox and unread tags removed
-function! s:NM_show_advance_marking_read_and_archiving()
-        let advance_tags = ['unread', 'inbox']
-
-        let vis_top = line('w0')
-        let vis_bot = line('w$')
-
-        let msg_top = <SID>NM_show_get_message_for_line(vis_top)
-        if !has_key(msg_top,'id')
-                throw "No top visible message."
-        endif
-
-        " if the top message is the last message, just expunge the entire thread and move on
-        if msg_top['end'] == line('$')
-                let ids = []
-                for msg in b:nm_raw_info['msgs']
-                        if has_key(msg,'match') && msg['match'] != '0'
-                                call add(ids, msg['id'])
-                        endif
-                endfor
-                let filter = <SID>NM_combine_tags('tag:', advance_tags, 'OR', '()')
-                         \ + ['AND']
-                         \ + <SID>NM_combine_tags('', ids, 'OR', '()')
-                call map(advance_tags, '"-" . v:val')
-                call <SID>NM_tag(filter, advance_tags)
-                call <SID>NM_show_next(1, 1)
-                return
-        endif
-
-        let msg_bot = <SID>NM_show_get_message_for_line(vis_bot)
-        if !has_key(msg_bot,'id')
-                throw "No bottom visible message."
-        endif
-
-        " if entire message fits on the screen, read/archive it, move to the next one
-        if msg_top['id'] != msg_bot['id'] || msg_top['end'] <= vis_bot
-                exec printf('norm %dG', vis_top)
-                call <SID>NM_show_next(0, 1)
-                if has_key(msg_top,'match') && msg_top['match'] != '0'
-                        redraw
-                        " do this last to hide the latency
-                        let filter = <SID>NM_combine_tags('tag:', advance_tags, 'OR', '()')
-                                 \ + ['AND', msg_top['id']]
-                        call map(advance_tags, '"-" . v:val')
-                        call <SID>NM_tag(filter, advance_tags)
-                endif
-                return
-        endif
-
-        " entire message does not fit on the screen, scroll down to bottom, max 1/2 screen
-        let jmp = winheight(winnr()) / 2
-        let max = msg_bot['end'] - vis_bot
-        if jmp > max
-                let jmp = max
-        endif
-        exec printf('norm %dGzt', vis_top + jmp)
-        return
-endfunction
-
-function! s:NM_show_pipe_message()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_previous_fold()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_next_fold()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_show_toggle_fold()
-        echo 'not implemented'
-endfunction
-
-
-" --- --- show screen helper functions {{{2
-
-function! s:NM_show_get_message_for_line(line)
-        for msg in b:nm_raw_info['msgs']
-                if a:line > msg['end']
-                        continue
-                endif
-                return msg
-        endfor
-        return {}
-endfunction
-
-function! s:NM_show_message_id()
-        if !exists('b:nm_raw_info')
-                throw 'Eeek! no b:nm_raw_info'
-        endif
-        let msg = <SID>NM_show_get_message_for_line(line('.'))
-        if has_key(msg,'id')
-                return msg['id']
-        endif
-        return ''
-endfunction
-
-function! s:NM_show_fold_toggle(key, type, fold)
-        let info = b:nm_raw_info
-        let act = 'open'
-        if a:fold
-                let act = 'close'
-        endif
-        for fld in info['folds']
-                if fld[0] != a:type
-                        continue
-                endif
-                "let idx = fld[3]
-                "let msg = info['msgs'][idx]
-                "if has_key(msg,'match') && msg['match'] == '0'
-                "        continue
-                "endif
-                let cls = foldclosed(fld[1])
-                if cls != -1 && cls != fld[1]
-                        continue
-                endif
-                exec printf('%dfold%s', fld[1], act)
-        endfor
-        exec printf('nnoremap <buffer> %s :call <SID>NM_show_fold_toggle(''%s'', ''%s'', %d)<CR>', a:key, a:key, a:type, !a:fold)
-endfunction
-
-
-" s:NM_cmd_show_parse returns the following dictionary:
-"    'disp':     lines to display
-"    'msgs':     message info dicts { start, end, id, depth, filename, descr, header }
-"    'folds':    fold info arrays [ type, start, end ]
-"    'foldtext': fold text indexed by start line
-function! s:NM_cmd_show_parse(inlines)
-        let info = { 'disp': [],       
-                   \ 'msgs': [],       
-                   \ 'folds': [],      
-                   \ 'foldtext': {} }  
-        let msg = {}
-        let hdr = {}
-
-        let in_message = 0
-        let in_header = 0
-        let in_body = 0
-        let in_part = ''
-
-        let body_start = -1
-        let part_start = -1
-
-        let mode_type = ''
-        let mode_start = -1
-
-        let inlnum = 0
-        for line in a:inlines
-                let inlnum = inlnum + 1
-                let foldinfo = []
-
-                if strlen(in_part)
-                        let part_end = 0
-
-                        if match(line, g:notmuch_show_part_end_regexp) != -1
-                                let part_end = len(info['disp'])
-                        else
-                                call add(info['disp'], line)
-                        endif
-
-                        if in_part == 'text/plain'
-                                if !part_end && mode_type == ''
-                                        if match(line, g:notmuch_show_signature_regexp) != -1
-                                                let mode_type = 'sig'
-                                                let mode_start = len(info['disp'])
-                                        elseif match(line, g:notmuch_show_citation_regexp) != -1
-                                                let mode_type = 'cit'
-                                                let mode_start = len(info['disp'])
-                                        endif
-                                elseif mode_type == 'cit'
-                                        if part_end || match(line, g:notmuch_show_citation_regexp) == -1
-                                                let outlnum = len(info['disp'])
-                                                if !part_end
-                                                        let outlnum = outlnum - 1
-                                                endif
-                                                let foldinfo = [ mode_type, mode_start, outlnum, len(info['msgs']),
-                                                               \ printf('[ %d-line citation.  Press "c" to show. ]', 1 + outlnum - mode_start) ]
-                                                let mode_type = ''
-                                        endif
-                                elseif mode_type == 'sig'
-                                        let outlnum = len(info['disp'])
-                                        if (outlnum - mode_start) > g:notmuch_show_signature_lines_max
-                                                let mode_type = ''
-                                        elseif part_end
-                                                let foldinfo = [ mode_type, mode_start, outlnum, len(info['msgs']),
-                                                               \ printf('[ %d-line signature.  Press "i" to show. ]', 1 + outlnum - mode_start) ]
-                                                let mode_type = ''
-                                        endif
-                                endif
-                        endif
-
-                        if part_end
-                                " FIXME: this is a hack for handling two folds being added for one line
-                                "         we should handle adding a fold in a function
-                                if len(foldinfo) && foldinfo[1] < foldinfo[2]
-                                        call add(info['folds'], foldinfo[0:3])
-                                        let info['foldtext'][foldinfo[1]] = foldinfo[4]
-                                endif
-
-                                let foldinfo = [ 'text', part_start, part_end, len(info['msgs']),
-                                               \ printf('[ %d-line %s.  Press "p" to show. ]', part_end - part_start, in_part) ]
-                                let in_part = ''
-                                call add(info['disp'], '')
-                        endif
-
-                elseif in_body
-                        if !has_key(msg,'body_start')
-                                let msg['body_start'] = len(info['disp']) + 1
-                        endif
-                        if match(line, g:notmuch_show_body_end_regexp) != -1
-                                let body_end = len(info['disp'])
-                                let foldinfo = [ 'bdy', body_start, body_end, len(info['msgs']),
-                                               \ printf('[ BODY %d - %d lines ]', len(info['msgs']), body_end - body_start) ]
-
-                                let in_body = 0
-
-                        elseif match(line, g:notmuch_show_part_begin_regexp) != -1
-                                let m = matchlist(line, 'ID: \(\d\+\), Content-type: \(\S\+\)')
-                                let in_part = 'unknown'
-                                if len(m)
-                                        let in_part = m[2]
-                                endif
-                                call add(info['disp'],
-                                         \ printf('--- %s ---', in_part))
-                                " We don't yet handle nested parts, so pop
-                                " multipart/* immediately so text/plain
-                                " sub-parts are parsed properly
-                                if match(in_part, '^multipart/') != -1
-                                        let in_part = ''
-                                else
-                                        let part_start = len(info['disp']) + 1
-                                endif
-                        endif
-
-                elseif in_header
-                        if in_header == 1
-                                let msg['descr'] = line
-                                call add(info['disp'], line)
-                                let in_header = 2
-                                let msg['hdr_start'] = len(info['disp']) + 1
-
-                        else
-                                if match(line, g:notmuch_show_header_end_regexp) != -1
-                                        let hdr_start = msg['hdr_start']+1
-                                        let hdr_end = len(info['disp'])
-                                        let foldinfo = [ 'hdr', hdr_start, hdr_end, len(info['msgs']),
-                                               \ printf('[ %d-line headers.  Press "h" to show. ]', hdr_end + 1 - hdr_start) ]
-                                        let msg['header'] = hdr
-                                        let in_header = 0
-                                        let hdr = {}
-                                else
-                                        let m = matchlist(line, '^\(\w\+\):\s*\(.*\)$')
-                                        if len(m)
-                                                let hdr[m[1]] = m[2]
-                                                if match(g:notmuch_show_headers, m[1]) != -1
-                                                        call add(info['disp'], line)
-                                                endif
-                                        endif
-                                endif
-                        endif
-
-                elseif in_message
-                        if match(line, g:notmuch_show_message_end_regexp) != -1
-                                let msg['end'] = len(info['disp'])
-                                call add(info['disp'], '')
-
-                                let foldinfo = [ 'msg', msg['start'], msg['end'], len(info['msgs']),
-                                               \ printf('[ MSG %d - %s ]', len(info['msgs']), msg['descr']) ]
-
-                                call add(info['msgs'], msg)
-                                let msg = {}
-                                let in_message = 0
-                                let in_header = 0
-                                let in_body = 0
-                                let in_part = ''
-
-                        elseif match(line, g:notmuch_show_header_begin_regexp) != -1
-                                let in_header = 1
-                                continue
-
-                        elseif match(line, g:notmuch_show_body_begin_regexp) != -1
-                                let body_start = len(info['disp']) + 1
-                                let in_body = 1
-                                continue
-                        endif
-
-                else
-                        if match(line, g:notmuch_show_message_begin_regexp) != -1
-                                let msg['start'] = len(info['disp']) + 1
-
-                                let m = matchlist(line, g:notmuch_show_message_parse_regexp)
-                                if len(m)
-                                        let msg['id'] = m[1]
-                                        let msg['depth'] = m[2]
-                                        let msg['match'] = m[3]
-                                        let msg['excluded'] = m[4]
-                                        let msg['filename'] = m[5]
-                                endif
-
-                                let in_message = 1
-                        endif
-                endif
-
-                if len(foldinfo) && foldinfo[1] < foldinfo[2]
-                        call add(info['folds'], foldinfo[0:3])
-                        let info['foldtext'][foldinfo[1]] = foldinfo[4]
-                endif
-        endfor
-        return info
-endfunction
-
-function! s:NM_cmd_show_mkfolds()
-        let info = b:nm_raw_info
-
-        for afold in info['folds']
-                exec printf('%d,%dfold', afold[1], afold[2])
-                let state = 'open'
-                if (afold[0] == 'sig' && g:notmuch_show_fold_signatures)
-                 \ || (afold[0] == 'cit' && g:notmuch_show_fold_citations)
-                 \ || (afold[0] == 'bdy' && g:notmuch_show_fold_bodies)
-                 \ || (afold[0] == 'hdr' && g:notmuch_show_fold_headers)
-                        let state = 'close'
-                elseif afold[0] == 'msg'
-                        let idx = afold[3]
-                        let msg = info['msgs'][idx]
-                        if has_key(msg,'match') && msg['match'] == '0'
-                                let state = 'close'
-                        endif
-                endif
-                exec printf('%dfold%s', afold[1], state)
-        endfor
-endfunction
-
-function! s:NM_cmd_show_mksyntax()
-        let info = b:nm_raw_info
-        let cnt = 0
-        for msg in info['msgs']
-                let cnt = cnt + 1
-                let start = msg['start']
-                let hdr_start = msg['hdr_start']
-                let body_start = msg['body_start']
-                let end = msg['end']
-                exec printf('syntax region nmShowMsg%dDesc start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgDesc', cnt, start, start+1)
-                exec printf('syntax region nmShowMsg%dHead start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgHead', cnt, hdr_start, body_start)
-                exec printf('syntax region nmShowMsg%dBody start=''\%%%dl'' end=''\%%%dl'' contains=@nmShowMsgBody', cnt, body_start, end)
-        endfor
-endfunction
-
-function! NM_cmd_show_foldtext()
-        let foldtext = b:nm_raw_info['foldtext']
-        return foldtext[v:foldstart]
-endfunction
-
-
-" --- implement compose screen {{{1
-
-function! s:NM_cmd_compose(words, body_lines)
-        let lines = []
-        let start_on_line = 0
-
-        let hdrs = { }
-        for word in a:words
-                let m = matchlist(word, '^\(\w[^:]*\):\s*\(.*\)\s*$')
-                if !len(m)
-                        throw 'Eeek! bad parameter ''' . string(word) . ''''
-                endif
-                let key = substitute(m[1], '\<\w', '\U&', 'g')
-                if !has_key(hdrs, key)
-                        let hdrs[key] = []
-                endif
-                if strlen(m[2])
-                        call add(hdrs[key], m[2])
-                endif
-        endfor
-
-        if !has_key(hdrs, 'From') || !len(hdrs['From'])
-                let me = <SID>NM_compose_get_user_email()
-                let hdrs['From'] = [ me ]
-        endif
-
-        for key in g:notmuch_compose_headers
-                let text = has_key(hdrs, key) ? join(hdrs[key], ', ') : ''
-                call add(lines, key . ': ' . text)
-                if !start_on_line && !strlen(text)
-                        let start_on_line = len(lines)
-                endif
-        endfor
-
-        for [key,val] in items(hdrs)
-                if match(g:notmuch_compose_headers, key) == -1
-                        let line = key . ': ' . join(val, ', ')
-                        call add(lines, line)
-                endif
-        endfor
-
-        call add(lines, '')
-        if !start_on_line
-                let start_on_line = len(lines) + 1
-        endif
-
-        if len(a:body_lines)
-                call extend(lines, a:body_lines)
-        else
-                call extend(lines, [ '', '' ])
-        endif
-
-        call <SID>NM_newComposeBuffer(lines, start_on_line)
-endfunction
-
-function! s:NM_compose_send()
-        call <SID>NM_assert_buffer_type('compose')
-        let fname = expand('%')
-        let lnum = 1
-        let line = getline(lnum)
-        let lst_hdr = ''
-        while match(line, '^$') == -1
-                if !exists("hdr_starts") && match(line, '^Notmuch-Help:') == -1
-                        let hdr_starts = lnum - 1
-                endif
-                let lnum = lnum + 1
-                let line = getline(lnum)
-        endwhile
-        let body_starts = lnum - 1
-
-        call append(body_starts, 'Date: ' . strftime('%a, %d %b %Y %H:%M:%S %z'))
-        exec printf(':0,%dd', hdr_starts)
-        write
-
-        let line = getline(1)
-        let m = matchlist(line, '^From:\s*\(.*\)\s*<\(.*\)>$')
-        if (len(m) >= 2)
-                let from = m[2]
-        else
-                let m = matchlist(line, '^From:\s*\(.*\)$')
-                let from = m[1]
-        endif
-
-        let cmdtxt = g:notmuch_sendmail . ' -t -f ' . from . ' < ' . fname
-        let out = system(cmdtxt)
-        let err = v:shell_error
-        if err
-                undo
-                write
-                call <SID>NM_newBuffer('new', 'error',
-                            \ "While running...\n" .
-                            \ '  ' . cmdtxt . "\n" .
-                            \ "\n" .
-                            \ "Failed with...\n" .
-                            \ substitute(out, '^', '  ', 'g'))
-                echohl Error
-                echo 'Eeek! unable to send mail'
-                echohl None
-                return
-        endif
-
-        if !exists('b:nm_prev_bufnr')
-                bdelete
-        else
-                let prev_bufnr = b:nm_prev_bufnr
-                bdelete
-                if prev_bufnr == bufnr('%')
-                        exec printf("buffer %d", prev_bufnr)
-                endif
-        endif
-        call delete(fname)
-        echo 'Mail sent successfully.'
-endfunction
-
-function! s:NM_compose_attach()
-        echo 'not implemented'
-endfunction
-
-function! s:NM_compose_next_entry_area()
-        let lnum = line('.')
-        let hdr_end = <SID>NM_compose_find_line_match(1,'^$',1)
-        if lnum < hdr_end
-                let lnum = lnum + 1
-                let line = getline(lnum)
-                if match(line, '^\([^:]\+\):\s*$') == -1
-                        call cursor(lnum, strlen(line) + 1)
-                        return ''
-                endif
-                while match(getline(lnum+1), '^\s') != -1
-                        let lnum = lnum + 1
-                endwhile
-                call cursor(lnum, strlen(getline(lnum)) + 1)
-                return ''
-
-        elseif lnum == hdr_end
-                call cursor(lnum+1, strlen(getline(lnum+1)) + 1)
-                return ''
-        endif
-        if mode() == 'i'
-                if !getbufvar(bufnr('.'), '&et')
-                        return "\t"
-                endif
-               let space = ''
-               let shiftwidth = a:shiftwidth
-               let shiftwidth = shiftwidth - ((virtcol('.')-1) % shiftwidth)
-                " we assume no one has shiftwidth set to more than 40 :)
-                return '                                        '[0:shiftwidth]
-        endif
-endfunction
-
-" --- --- compose screen helper functions {{{2
-
-function! s:NM_compose_get_user_email()
-        " TODO: do this properly (still), i.e., allow for multiple email accounts
-        let email = substitute(system('notmuch config get user.primary_email'), '\v(^\s*|\s*$|\n)', '', 'g')
-       return email
-endfunction
-
-function! s:NM_compose_find_line_match(start, pattern, failure)
-        let lnum = a:start
-        let lend = line('$')
-        while lnum < lend
-                if match(getline(lnum), a:pattern) != -1
-                        return lnum
-                endif
-                let lnum = lnum + 1
-        endwhile
-        return a:failure
-endfunction
-
-
-" --- notmuch helper functions {{{1
-
-function! s:NM_newBuffer(how, type, content)
-        if strlen(a:how)
-                exec a:how
-        else
-                enew
-        endif
-        setlocal buftype=nofile readonly modifiable scrolloff=0 sidescrolloff=0
-        silent put=a:content
-        keepjumps 0d
-        setlocal nomodifiable
-        execute printf('set filetype=notmuch-%s', a:type)
-        execute printf('set syntax=notmuch-%s', a:type)
-        let b:nm_type = a:type
-endfunction
-
-function! s:NM_newFileBuffer(fdir, fname, type, lines)
-        let fdir = expand(a:fdir)
-        if !isdirectory(fdir)
-                call mkdir(fdir, 'p')
-        endif
-        let file_name = <SID>NM_mktemp(fdir, a:fname)
-        if writefile(a:lines, file_name)
-                throw 'Eeek! couldn''t write to temporary file ' . file_name
-        endif
-        exec printf('edit %s', file_name)
-        setlocal buftype= noreadonly modifiable scrolloff=0 sidescrolloff=0
-        execute printf('set filetype=notmuch-%s', a:type)
-        execute printf('set syntax=notmuch-%s', a:type)
-        let b:nm_type = a:type
-endfunction
-
-function! s:NM_newComposeBuffer(lines, start_on_line)
-        let lines = a:lines
-        let start_on_line = a:start_on_line
-        let real_hdr_start = 1
-        if g:notmuch_compose_header_help
-                let help_lines = [
-                  \ 'Notmuch-Help: Type in your message here; to help you use these bindings:',
-                  \ 'Notmuch-Help:   ,a    - attach a file',
-                  \ 'Notmuch-Help:   ,s    - send the message (Notmuch-Help lines will be removed)',
-                  \ 'Notmuch-Help:   ,q    - abort the message',
-                  \ 'Notmuch-Help:   <Tab> - skip through header lines',
-                  \ ]
-                call extend(lines, help_lines, 0)
-                let real_hdr_start = len(help_lines)
-                if start_on_line > 0
-                        let start_on_line = start_on_line + len(help_lines)
-                endif
-        endif
-        call extend(lines, g:notmuch_signature)
-
-
-        let prev_bufnr = bufnr('%')
-        setlocal bufhidden=hide
-        call <SID>NM_newFileBuffer(g:notmuch_compose_temp_file_dir, '%s.mail',
-                                  \ 'compose', lines)
-        setlocal bufhidden=hide
-        let b:nm_prev_bufnr = prev_bufnr
-
-        call <SID>NM_set_map('n', g:notmuch_compose_nmaps)
-        call <SID>NM_set_map('i', g:notmuch_compose_imaps)
-
-        if start_on_line > 0 && start_on_line <= len(lines)
-                call cursor(start_on_line, strlen(getline(start_on_line)) + 1)
-        else
-                call cursor(real_hdr_start, strlen(getline(real_hdr_start)) + 1)
-                call <SID>NM_compose_next_entry_area()
-        endif
-
-        if g:notmuch_compose_insert_mode_start
-                startinsert!
-        endif
-        echo 'Type your message, use <TAB> to jump to next header and then body.'
-endfunction
-
-function! s:NM_assert_buffer_type(type)
-        if !exists('b:nm_type') || b:nm_type != a:type
-                throw printf('Eeek! expected type %s, but got %s.', a:type,
-                            \ exists(b:nm_type) ? b:nm_type : 'something else')
-        endif
-endfunction
-
-function! s:NM_mktemp(dir, name)
-        let time_stamp = strftime('%Y%m%d-%H%M%S')
-        let file_name = substitute(a:dir,'/*$','/','') . printf(a:name, time_stamp)
-        " TODO: check if it exists, try again
-        return file_name
-endfunction
-
-function! s:NM_shell_escape(word)
-        " TODO: use shellescape()
-        let word = substitute(a:word, '''', '\\''', 'g')
-        return '''' . word . ''''
-endfunction
-
-" this function was taken from git.vim, then fixed up
-" http://github.com/motemen/git-vim
-function! s:NM_shell_split(cmd)
-        let l:split_cmd = []
-        let cmd = a:cmd
-        let iStart = 0
-        while 1
-                let t = match(cmd, '\S', iStart)
-                if t < iStart
-                        break
-                endif
-                let iStart = t
-
-                let iSpace = match(cmd, '\v(\s|$)', iStart)
-                if iSpace < iStart
-                        break
-                endif
-
-                let iQuote1 = match(cmd, '\(^["'']\|[^\\]\@<=["'']\)', iStart)
-                if iQuote1 > iSpace || iQuote1 < iStart
-                        let iEnd = iSpace - 1
-                        let l:split_cmd += [ cmd[iStart : iEnd] ]
-                else
-                        let q = cmd[iQuote1]
-                        let iQuote2 = match(cmd, '[^\\]\@<=[' . q . ']', iQuote1 + 1)
-                        if iQuote2 < iQuote1
-                                throw 'No matching ' . q . ' quote'
-                        endif
-                        let iEnd = iQuote2
-                        let l:split_cmd += [ cmd[iStart+1 : iEnd-1 ] ]
-                endif
-
-
-                let iStart = iEnd + 1
-        endwhile
-
-        return l:split_cmd
-endfunction
-
-
-function! s:NM_run(args)
-        let words = a:args
-        call map(words, 's:NM_shell_escape(v:val)')
-        let cmd = g:notmuch_cmd . ' ' . join(words) . '< /dev/null'
-
-        if exists('g:notmuch_debug') && g:notmuch_debug
-                let start = reltime()
-                let out = system(cmd)
-                let err = v:shell_error
-                let delta = reltime(start)
-
-                echo printf('[%s] {%s} %s', reltimestr(delta), string(err), string(cmd))
-        else
-                let out = system(cmd)
-                let err = v:shell_error
-        endif
-
-        if err
-                echohl Error
-                echo substitute(out, '\n*$', '', '')
-                echohl None
-                return ''
-        else
-                return out
-        endif
-endfunction
-
-" --- external mail handling helpers {{{1
-
-function! s:NM_new_mail()
-        call <SID>NM_cmd_compose([], [])
-endfunction
-
-" --- tag manipulation helpers {{{1
-
-" used to combine an array of words with prefixes and separators
-" example:
-"     NM_combine_tags('tag:', ['one', 'two', 'three'], 'OR', '()')
-"  -> ['(', 'tag:one', 'OR', 'tag:two', 'OR', 'tag:three', ')']
-function! s:NM_combine_tags(word_prefix, words, separator, brackets)
-        let res = []
-        for word in a:words
-                if len(res) && strlen(a:separator)
-                        call add(res, a:separator)
-                endif
-                call add(res, a:word_prefix . word)
-        endfor
-        if len(res) > 1 && strlen(a:brackets)
-                if strlen(a:brackets) != 2
-                        throw 'Eeek! brackets arg to NM_combine_tags must be 2 chars'
-                endif
-                call insert(res, a:brackets[0])
-                call add(res, a:brackets[1])
-        endif
-        return res
-endfunction
-
-" --- other helpers {{{1
-
-function! s:NM_get_search_words()
-        if !exists('b:nm_search_words')
-                throw 'Eeek! no b:nm_search_words'
-        endif
-        return b:nm_search_words
-endfunction
-
-function! s:NM_kill_this_buffer()
-        if exists('b:nm_prev_bufnr')
-                let prev_bufnr = b:nm_prev_bufnr
-                bdelete!
-                exec printf("buffer %d", prev_bufnr)
-        else
-                echo "This is the last buffer; use :q<CR> to quit."
-        endif
-endfunction
-
-function! s:NM_search_expand(arg)
-        let word = expand(a:arg)
-        let prev_bufnr = bufnr('%')
-        setlocal bufhidden=hide
-        call <SID>NM_cmd_search([word])
-        setlocal bufhidden=delete
-        let b:nm_prev_bufnr = prev_bufnr
-endfunction
-
-function! s:NM_tag(filter, tags)
-        let filter = len(a:filter) ? a:filter : [<SID>NM_search_thread_id()]
-        if !len(filter)
-                throw 'Eeek! I couldn''t find the thread id!'
-        endif
-        let args = ['tag']
-        call extend(args, a:tags)
-        call add(args, '--')
-        call extend(args, filter)
-        " TODO: handle errors
-        call <SID>NM_run(args)
-endfunction
-
-" --- process and set the defaults {{{1
-
-function! NM_set_defaults(force)
-        for [key, dflt] in items(s:notmuch_defaults)
-                let cmd = ''
-                if !a:force && exists(key) && type(dflt) == type(eval(key))
-                        continue
-                elseif type(dflt) == type(0)
-                        let cmd = printf('let %s = %d', key, dflt)
-                elseif type(dflt) == type('')
-                        let cmd = printf('let %s = ''%s''', key, dflt)
-                " FIXME: not sure why this didn't work when dflt is an array
-                "elseif type(dflt) == type([])
-                "        let cmd = printf('let %s = %s', key, string(dflt))
-                else
-                        echoe printf('E: Unknown type in NM_set_defaults(%d) using [%s,%s]',
-                                                \ a:force, key, string(dflt))
-                        continue
-                endif
-                exec cmd
-        endfor
-endfunction
-call NM_set_defaults(0)
-
-" for some reason NM_set_defaults() didn't work for arrays...
-if !exists('g:notmuch_show_headers')
-        let g:notmuch_show_headers = s:notmuch_show_headers_defaults
-endif
-if !exists('g:notmuch_initial_search_words')
-        let g:notmuch_initial_search_words = s:notmuch_initial_search_words_defaults
-endif
-if !exists('g:notmuch_folders')
-        let g:notmuch_folders = s:notmuch_folders_defaults
-endif
-
-if !exists('g:notmuch_signature')
-        let g:notmuch_signature = s:notmuch_signature_defaults
-endif
-if !exists('g:notmuch_compose_headers')
-        let g:notmuch_compose_headers = s:notmuch_compose_headers_defaults
-endif
-
-" --- assign keymaps {{{1
-
-function! s:NM_set_map(type, maps)
-        nmapclear
-        for [key, code] in items(a:maps)
-                exec printf('%snoremap <buffer> %s %s', a:type, key, code)
-        endfor
-        " --- this is a hack for development :)
-        nnoremap ,nmr :runtime! plugin/notmuch.vim<CR>
-endfunction
-
-" --- command handler {{{1
-
-function! NotMuch(args)
-        let args = a:args
-        if !strlen(args)
-                let args = 'folders'
-        endif
-
-        let words = <SID>NM_shell_split(args)
-        if words[0] == 'folders' || words[0] == 'f'
-                let words = words[1:]
-                call <SID>NM_cmd_folders(words)
-
-        elseif words[0] == 'search' || words[0] == 's'
-                if len(words) > 1
-                        let words = words[1:]
-                elseif exists('b:nm_search_words')
-                        let words = b:nm_search_words
-                else
-                        let words = g:notmuch_initial_search_words
-                endif
-                call <SID>NM_cmd_search(words)
-
-        elseif words[0] == 'show'
-                echoe 'show is not yet implemented.'
-
-        elseif words[0] == 'new' || words[0] == 'compose'
-                let words = words[1:]
-                call <SID>NM_cmd_compose(words, [])
-        endif
-endfunction
-function! CompleteNotMuch(arg_lead, cmd_line, cursor_pos)
-        return []
-endfunction
-
-
-" --- glue {{{1
-
-command! -nargs=* -complete=customlist,CompleteNotMuch NotMuch call NotMuch(<q-args>)
-cabbrev  notmuch <c-r>=(getcmdtype()==':' && getcmdpos()==1 ? 'NotMuch' : 'notmuch')<CR>
-
-" vim: set ft=vim ts=8 sw=8 et foldmethod=marker :
diff --git a/contrib/notmuch-vim/syntax/notmuch-compose.vim b/contrib/notmuch-vim/syntax/notmuch-compose.vim
deleted file mode 100644 (file)
index 19adb75..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-runtime! syntax/mail.vim
-
-syntax region nmComposeHelp          contains=nmComposeHelpLine start='^Notmuch-Help:\%1l' end='^\(Notmuch-Help:\)\@!'
-syntax match  nmComposeHelpLine      /Notmuch-Help:/ contained
-
-highlight link nmComposeHelp        Include
-highlight link nmComposeHelpLine    Error
diff --git a/contrib/notmuch-vim/syntax/notmuch-folders.vim b/contrib/notmuch-vim/syntax/notmuch-folders.vim
deleted file mode 100644 (file)
index 9477f86..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-" notmuch folders mode syntax file
-
-syntax region nmFoldersCount     start='^' end='\%10v'
-syntax region nmFoldersName      start='\%11v' end='\%31v'
-syntax match  nmFoldersSearch    /([^()]\+)$/
-
-highlight link nmFoldersCount     Statement
-highlight link nmFoldersName      Type
-highlight link nmFoldersSearch    String
-
-highlight CursorLine term=reverse cterm=reverse gui=reverse
-
diff --git a/contrib/notmuch-vim/syntax/notmuch-git-diff.vim b/contrib/notmuch-vim/syntax/notmuch-git-diff.vim
deleted file mode 100644 (file)
index 6f15fdc..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-syn match diffRemoved  "^-.*"
-syn match diffAdded    "^+.*"
-
-syn match diffSeparator        "^---$"
-syn match diffSubname  " @@..*"ms=s+3 contained
-syn match diffLine     "^@.*" contains=diffSubname
-
-syn match diffFile     "^diff .*"
-syn match diffNewFile  "^+++ .*"
-syn match diffOldFile  "^--- .*"
-
-hi def link diffOldFile                diffFile
-hi def link diffNewFile                diffFile
-
-hi def link diffFile           Type
-hi def link diffRemoved                Special
-hi def link diffAdded          Identifier
-hi def link diffLine           Statement
-hi def link diffSubname                PreProc
-
-syntax match gitDiffStatLine /^ .\{-}\zs[+-]\+$/ contains=gitDiffStatAdd,gitDiffStatDelete
-syntax match gitDiffStatAdd    /+/ contained
-syntax match gitDiffStatDelete /-/ contained
-
-hi def link gitDiffStatAdd diffAdded
-hi def link gitDiffStatDelete diffRemoved
diff --git a/contrib/notmuch-vim/syntax/notmuch-search.vim b/contrib/notmuch-vim/syntax/notmuch-search.vim
deleted file mode 100644 (file)
index f458d77..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-syntax region nmSearch         start=/^/ end=/$/               oneline contains=nmSearchDate
-syntax match nmSearchDate      /^.\{-13}/                      contained nextgroup=nmSearchNum
-syntax match nmSearchNum       /.\{-4}/                        contained nextgroup=nmSearchFrom
-syntax match nmSearchFrom      /.\{-21}/                       contained nextgroup=nmSearchSubject
-syntax match nmSearchSubject   /.\{0,}\(([^()]\+)$\)\@=/       contained nextgroup=nmSearchTags
-syntax match nmSearchTags      /.\+$/                          contained
-
-highlight link nmSearchDate    Statement
-highlight link nmSearchNum     Type
-highlight link nmSearchFrom    Include
-highlight link nmSearchSubject Normal
-highlight link nmSearchTags    String
diff --git a/contrib/notmuch-vim/syntax/notmuch-show.vim b/contrib/notmuch-vim/syntax/notmuch-show.vim
deleted file mode 100644 (file)
index c3a98b7..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-" notmuch show mode syntax file
-
-syntax cluster nmShowMsgDesc contains=nmShowMsgDescWho,nmShowMsgDescDate,nmShowMsgDescTags
-syntax match   nmShowMsgDescWho /[^)]\+)/ contained
-syntax match   nmShowMsgDescDate / ([^)]\+[0-9]) / contained
-syntax match   nmShowMsgDescTags /([^)]\+)$/ contained
-
-syntax cluster nmShowMsgHead contains=nmShowMsgHeadKey,nmShowMsgHeadVal
-syntax match   nmShowMsgHeadKey /^[^:]\+: / contained
-syntax match   nmShowMsgHeadVal /^\([^:]\+: \)\@<=.*/ contained
-
-syntax cluster nmShowMsgBody contains=@nmShowMsgBodyMail,@nmShowMsgBodyGit
-syntax include @nmShowMsgBodyMail syntax/mail.vim
-
-silent! syntax include @nmShowMsgBodyGit syntax/notmuch-git-diff.vim
-
-highlight nmShowMsgDescWho term=reverse cterm=reverse gui=reverse
-highlight link nmShowMsgDescDate Type
-highlight link nmShowMsgDescTags String
-
-highlight link nmShowMsgHeadKey  Macro
-"highlight link nmShowMsgHeadVal  NONE
-
-highlight Folded term=reverse ctermfg=LightGrey ctermbg=Black guifg=LightGray guibg=Black
index 3dabc97b4346f6304f2e0fbf925efabf87d03d8c..3e8ce7ca07740b3aea9491dee18c7859b8ac4df5 100644 (file)
--- a/crypto.c
+++ b/crypto.c
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Authors: Jameson Rollins <jrollins@finestructure.net>
  */
index 2cd2421394294fbfbbcaf9ad229a3bac1e06e731..4027a79b63b355c7fbe712b1fa87ddf120f94d7c 100644 (file)
@@ -27,9 +27,9 @@ Build-Depends:
  gnupg <!nocheck>,
  bash-completion (>=1.9.0~)
 Standards-Version: 3.9.6
-Homepage: http://notmuchmail.org/
+Homepage: https://notmuchmail.org/
 Vcs-Git: git://notmuchmail.org/git/notmuch
-Vcs-Browser: http://git.notmuchmail.org/git/notmuch
+Vcs-Browser: https://git.notmuchmail.org/git/notmuch
 
 Package: notmuch
 Architecture: any
index e8b9378e73c31ce327bf93888052d3893223d52f..0fa0fb6bda23881ed9c01b42f974f6e4b652526b 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Chris Wilson <chris@chris-wilson.co.uk>
  */
index ea284d083146a889234e710b84d143898139bf53..a3c40695c4ea778ba1d373bbf75bc86f17126a41 100755 (executable)
@@ -66,7 +66,7 @@ while (my ($k, $v) = each %fhash)
 
     my @lines;
     open I, '-|', qw/env -i/, "PATH=$ENV{PATH}",
-       qw/TERM=vt100 LANG=en_US.utf8 LC_ALL=en_US.utf8/,
+       qw/TERM=vt100 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8/,
        qw/GROFF_NO_SGR=1 MAN_KEEP_FORMATTING=1 MANWIDTH=80/,
        qw/man/, $v or die "$!";
     binmode I, ':utf8';
@@ -200,6 +200,6 @@ foreach (sort srt values %fhash)
 print <<'EOF';
 
 The manual pages are licensed under
-[the GNU General Public License](http://www.gnu.org/licenses/gpl.txt),
+[the GNU General Public License](https://www.gnu.org/licenses/gpl.txt),
 either version 3.0 or at your option any later version.
 EOF
index 4b5f84a82e9f812aa37fb14923127179ef2a2c6c..1207a4aa107cb874c34076d0921a2958d201de9a 100644 (file)
@@ -92,7 +92,7 @@ EXAMPLE
   {
     "meta": {
       "title": "Notmuch Patches",
-      "blurb": "For more information see <a href=\"http://notmuchmail.org/nmbug\">nmbug</a>",
+      "blurb": "For more information see <a href=\"https://notmuchmail.org/nmbug\">nmbug</a>",
       "header": "<html><head></head><body><h1>{title}</h1><p>{blurb}</p><h2>Views</h2>",
       "footer": "<hr><p>Generated: {datetime}</p></html>",
       "message-url": "http://mid.gmane.org/{message-id}"
index 0787b2ba9137f7ef01bfef7f0ac18b7fc46dc54a..1dd5f14fe7b4dbaf01a4fdfbd1d5332ae15b983a 100755 (executable)
@@ -14,7 +14,7 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see http://www.gnu.org/licenses/ .
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
 
 """
 Manage notmuch tags with Git
@@ -80,7 +80,7 @@ except AttributeError:  # Python < 3.2
 
         See PEP 343 for details on context managers [1].
 
-        [1]: http://legacy.python.org/dev/peps/pep-0343/
+        [1]: https://www.python.org/dev/peps/pep-0343/
         """
         def __init__(self, **kwargs):
             self.name = _tempfile.mkdtemp(**kwargs)
@@ -119,9 +119,9 @@ def _xapian_quote(string):
     Xapian uses double-quotes for quoting strings.  You can escape
     internal quotes by repeating them [1,2,3].
 
-    [1]: http://trac.xapian.org/ticket/128#comment:2
-    [2]: http://trac.xapian.org/ticket/128#comment:17
-    [3]: http://trac.xapian.org/changeset/13823/svn
+    [1]: https://trac.xapian.org/ticket/128#comment:2
+    [2]: https://trac.xapian.org/ticket/128#comment:17
+    [3]: https://trac.xapian.org/changeset/13823/svn
     """
     return '"{0}"'.format(string.replace('"', '""'))
 
index 87390c1e32c430f26fe987613572aea429b604aa..a9c2a6ec5bf4267f6a16c9ab030409c6bad3c3e3 100755 (executable)
@@ -17,7 +17,7 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see http://www.gnu.org/licenses/ .
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
 
 """Generate text and/or HTML for one or more notmuch searches.
 
index 48b6f19f5ead26da825bd0e4893ea7ac36a55da4..c5b35b1797f8bc754b80ce93b816d47520e88de2 100644 (file)
@@ -1,7 +1,7 @@
 {
     "meta": {
         "title": "Notmuch Patches",
-        "blurb": "For more information see <a href=\"http://notmuchmail.org/nmbug\">nmbug</a>"
+        "blurb": "For more information see <a href=\"https://notmuchmail.org/nmbug\">nmbug</a>"
     },
 
     "views": [
index b0a62c25b28f1e13a34a3ea04b21887ebb79cd16..041f6216fbbf293554b480e50819f057ad009842 100755 (executable)
@@ -7,7 +7,7 @@
 ;;
 ;; Authors: Tomi Ollila <tomi.ollila@iki.fi>
 ;;
-;; http://www.emacswiki.org/emacs/EmacsScripts was a useful starting point...
+;; https://www.emacswiki.org/emacs/EmacsScripts was a useful starting point...
 ;;
 ;; Licence: GPLv3+
 ;;
index c033f344d3c3780dc0a50a1fc3036f06c354c1ec..2ca15d41e7a824acb876536b144f9b4b5ec15790 100644 (file)
@@ -177,7 +177,7 @@ FORMULA_FONTSIZE       = 10
 FORMULA_TRANSPARENT    = YES
 USE_MATHJAX            = NO
 MATHJAX_FORMAT         = HTML-CSS
-MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+MATHJAX_RELPATH        = https://cdn.mathjax.org/mathjax/latest
 MATHJAX_EXTENSIONS     =
 MATHJAX_CODEFILE       =
 SEARCHENGINE           = YES
index 3f0e6e654c1431a1b2a393589a46b520fd201315..344606d9e1814434dac78a722a138bb204adf19f 100644 (file)
@@ -14,6 +14,7 @@ Contents:
    man1/notmuch-count
    man1/notmuch-dump
    notmuch-emacs
+   man1/notmuch-emacs-mua
    man5/notmuch-hooks
    man1/notmuch-insert
    man1/notmuch-new
index 40c12721d2918773feff77c2ce1094f99f43a9cd..5a517ebda95ac290a06f11783f38df73f45a37b2 100644 (file)
@@ -132,6 +132,17 @@ The available configuration items are described below.
     
         Default: ``gpg``.
 
+    **built_with.<name>**
+
+       Compile time feature <name>. Current possibilities include
+       "compact" (see **notmuch-compact(1)**)
+       and "field_processor" (see **notmuch-search-terms(7)**).
+
+    **query.<name>**
+
+        Expansion for named query called <name>. See
+        **notmuch-search-terms(7)** for more information about named
+        queries.
 
 ENVIRONMENT
 ===========
index a37c337c72e6a9e3189704006ca332546d3772b1..94986a86bba5b6dfaee06b42e2160f8917c1eea3 100644 (file)
@@ -71,6 +71,31 @@ Supported options for **dump** include
             characters. Note also that tags with spaces will not be
             correctly restored with this format.
 
+    ``--include=(config|tags)``
+
+    Control what kind of metadata is included in the output.
+
+      **config**
+
+       Output configuration data stored in the database. Each line
+       starts with "#@ ", followed by a space seperated key-value
+       pair.  Both key and value are hex encoded if needed.
+
+      **tags**
+
+       Output per-message metadata, namely tags. See *format* above
+       for description of the output.
+
+      The default is to include both tags and configuration
+      information. As of version 2 of the dump format, there is a
+      header line of the following form
+
+      |
+      |  #notmuch-dump <*format*>:<*version*> <*included*>
+
+      where <*included*> is a comma separated list of the above
+      options.
+
     ``--output=``\ <filename>
         Write output to given file instead of stdout.
 
index 362e2629e5746960af6d539606655d07bc3847d7..87fa22ef72c11b5457e03288749fb1b415cc2741 100644 (file)
@@ -50,6 +50,24 @@ Supported options for **restore** include
             format, this heuristic, based the fact that batch-tag format
             contains no parentheses, should be accurate.
 
+    ``--include=(config|tags)``
+
+      Control what kind of metadata is restored.
+
+       **config**
+
+         Restore configuration data to the database. Each configuration line starts
+         with "#@ ", followed by a space seperated key-value pair.
+         Both key and value are hex encoded if needed.
+
+       **tags**
+
+         Output per-message metadata, namely tags. See *format* above
+         for more details.
+
+      The default is to restore both tags and configuration
+      information
+
     ``--input=``\ <filename>
         Read input from given file instead of stdin.
 
index 3acfbdb436db953f3889e98e75f913135ae06858..7429f517626db277bce71af1f32cc2d14d6b04b1 100644 (file)
@@ -29,7 +29,7 @@ While the command-line program ``notmuch`` provides powerful
 functionality, it does not provide the most convenient interface for
 that functionality. More sophisticated interfaces are expected to be
 built on top of either the command-line interface, or more likely, on
-top of the notmuch library interface. See http://notmuchmail.org for
+top of the notmuch library interface. See https://notmuchmail.org for
 more about alternate interfaces to notmuch. The emacs-based interface to
 notmuch (available under **emacs/** in the Notmuch source distribution)
 is probably the most widely used at this time.
@@ -138,13 +138,13 @@ of notmuch.
 SEE ALSO
 ========
 
-**notmuch-config(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**,
-**notmuch-address(1)**
+**notmuch-address(1)**, **notmuch-compact(1)**, **notmuch-config(1)**,
+**notmuch-count(1)**, **notmuch-dump(1)**, **notmuch-hooks(5)**,
+**notmuch-insert(1)**, **notmuch-new(1)**, **notmuch-reply(1)**,
+**notmuch-restore(1)**, **notmuch-search(1)**,
+**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
 
-The notmuch website: **http://notmuchmail.org**
+The notmuch website: **https://notmuchmail.org**
 
 CONTACT
 =======
index 2fbc16d133722bec317352278f71e068bd6a158f..075f88c8fc6ede178f3561b77a2306553c3a26f7 100644 (file)
@@ -56,6 +56,8 @@ indicate user-supplied values):
 
 -  lastmod:<initial-revision>..<final-revision>
 
+-  query:<name>
+
 The **from:** prefix is used to match the name or address of the sender
 of an email message.
 
@@ -132,6 +134,11 @@ were added/removed or filenames changed).  This is usually used in
 conjunction with the **--uuid** argument to **notmuch search**
 to find messages that have changed since an earlier query.
 
+The **query:** prefix allows queries to refer to previously saved
+queries added with **notmuch-config(1)**. Named queries are only
+available if notmuch is built with **Xapian Field Processors** (see
+below).
+
 Operators
 ---------
 
@@ -208,15 +215,11 @@ Boolean and Probabilistic Prefixes
 Xapian (and hence notmuch) prefixes are either **boolean**, supporting
 exact matches like "tag:inbox"  or **probabilistic**, supporting a more flexible **term** based searching. The prefixes currently supported by notmuch are as follows.
 
-+------------------+-----------------------+
-|Boolean           |Probabilistic          |
-+------------------+-----------------------+
-| **tag:** **id:** | **from:** **to:**     |
-|**thread:**       |**subject:**           |
-|**folder:**       |**attachment:**        |
-|**path:**         |**mimetype:**          |
-|                  |                       |
-+------------------+-----------------------+
+
+Boolean
+   **tag:**, **id:**, **thread:**, **folder:**, **path:**
+Probabilistic
+   **from:**, **to:**, **subject:**, **attachment:**, **mimetype:**
 
 Terms and phrases
 -----------------
@@ -281,9 +284,10 @@ matches from the beginning of January to the end of February.
 date:<expr>..! can be used as a shorthand for date:<expr>..<expr>. The
 expansion takes place before interpretation, and thus, for example,
 date:monday..! matches from the beginning of Monday until the end of
-Monday. (Note that entering date:<expr> without "..", for example
-date:yesterday, won't work, as it's not interpreted as a range
-expression at all. Again, use date:yesterday..!)
+Monday.
+With **Xapian Field Processor** support (see below), non-range
+date queries such as date:yesterday will work, but otherwise
+will give unexpected results; if in doubt use date:yesterday..!
 
 Currently, we do not support spaces in range expressions. You can
 replace the spaces with '\_', or (in most cases) '-', or (in some cases)
@@ -370,6 +374,22 @@ Time zones
 
 Some time zone codes, e.g. UTC, EET.
 
+XAPIAN FIELD PROCESSORS
+=======================
+
+Certain optional features of the notmuch query processor rely on the
+presence of the Xapian field processor API. You can determine if your
+notmuch was built against a sufficiently recent version of Xapian by running
+
+::
+
+  % notmuch config get built_with.field_processor
+
+Currently the following features require field processor support:
+
+- non-range date queries, e.g. "date:today"
+- named queries e.g. "query:my_special_query"
+
 SEE ALSO
 ========
 
index 6f2f61e9214a3987dd2cd35f8c186030c3b3eb4a..d68542d349a017b1fd6acb2bfd7400a38385bd41 100644 (file)
@@ -42,11 +42,8 @@ a mouse or by positioning the cursor and pressing ``<return>``
 |
 | All tags: **[show]**
 |
-|       Type a search query and hit RET to view matching threads.
-|              Edit saved searches with the ``edit`` button.
-| Hit RET or click on a saved search or tag name to view matching threads.
-|     ``=`` to refresh this screen. ``s`` to search messages. ``q`` to quit.
-|                  **Customize** this page.
+|       Hit \`?' for context-sensitive help in any Notmuch screen.
+|                    Customize Notmuch or this page.
 
 You can change the overall appearance of the notmuch-hello screen by
 customizing the variable :index:`notmuch-hello-sections`.
index 77550602e5e1c08132b43a456b4d229fc40b2a1b..350d537fb264273d6f4277eb3e0941d13c6d8d29 100644 (file)
@@ -21,7 +21,7 @@
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
index 24c1a45745821cc1840f9cd404e6542d66e3dedb..5b6db6986dc0953d41a3564ede46a6aba7ffc11c 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Austin Clements <aclements@csail.mit.edu>
 
index aafbe5fb8328a4ce1b92a9aa482ffa36dbd7fd6e..10eaab196267bbad86c967e1318ea6083a577b44 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: David Edmondson <dme@dme.org>
 
 ;;
 (declare-function company-manual-begin "company")
 
+(defvar notmuch-address-last-harvest 0
+  "Time of last address harvest")
+
+(defvar notmuch-address-completions (make-hash-table :test 'equal)
+  "Hash of email addresses for completion during email composition.
+  This variable is set by calling `notmuch-address-harvest'.")
+
+(defvar notmuch-address-full-harvest-finished nil
+  "t indicates that full completion address harvesting has been
+finished")
+
 (defcustom notmuch-address-command 'internal
-  "The command which generates possible addresses. It must take a
-single argument and output a list of possible matches, one per
-line. The default value of `internal' uses built-in address
-completion."
+  "Determines how address completion candidates are generated.
+
+If it is a string then that string should be an external program
+which must take a single argument (searched string) and output a
+list of completion candidates, one per line.
+
+Alternatively, it can be the symbol 'internal, in which case
+internal completion is used; the variable
+`notmuch-address-internal-completion` can be used to customize
+this case.
+
+Finally, if this variable is nil then address completion is
+disabled."
   :type '(radio
          (const :tag "Use internal address completion" internal)
          (const :tag "Disable address completion" nil)
-         (string :tag "Use external completion command" "notmuch-addresses"))
+         (string :tag "Use external completion command"))
+  :group 'notmuch-send
+  :group 'notmuch-external)
+
+(defcustom notmuch-address-internal-completion '(sent nil)
+  "Determines how internal address completion generates candidates.
+
+This should be a list of the form '(DIRECTION FILTER), where
+ DIRECTION is either sent or received and specifies whether the
+ candidates are searched in messages sent by the user or received
+ by the user (note received by is much faster), and FILTER is
+ either nil or a filter-string, such as \"date:1y..\" to append
+ to the query."
+  :type '(list :tag "Use internal address completion"
+              (radio
+               :tag "Base completion on messages you have"
+               :value sent
+               (const :tag "sent (more accurate)" sent)
+               (const :tag "received (faster)" received))
+              (radio :tag "Filter messages used for completion"
+                     (const :tag "Use all messages" nil)
+                     (string :tag "Filter query")))
+  ;; We override set so that we can clear the cache when this changes
+  :set (lambda (symbol value)
+        (set-default symbol value)
+        (setq notmuch-address-last-harvest 0)
+        (setq notmuch-address-completions (clrhash notmuch-address-completions))
+        (setq notmuch-address-full-harvest-finished nil))
   :group 'notmuch-send
   :group 'notmuch-external)
 
@@ -51,17 +98,6 @@ to know how address selection is made by default."
   :group 'notmuch-send
   :group 'notmuch-external)
 
-(defvar notmuch-address-last-harvest 0
-  "Time of last address harvest")
-
-(defvar notmuch-address-completions (make-hash-table :test 'equal)
-  "Hash of email addresses for completion during email composition.
-  This variable is set by calling `notmuch-address-harvest'.")
-
-(defvar notmuch-address-full-harvest-finished nil
-  "t indicates that full completion address harvesting has been
-finished")
-
 (defun notmuch-address-selection-function (prompt collection initial-input)
   "Call (`completing-read'
       PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
@@ -82,19 +118,30 @@ finished")
   :group 'notmuch-send)
 
 (defun notmuch-address-setup ()
-  (let* ((use-company (and notmuch-address-use-company
-                          (eq notmuch-address-command 'internal)
+  (let* ((setup-company (and notmuch-address-use-company
                           (require 'company nil t)))
         (pair (cons notmuch-address-completion-headers-regexp
-                    (if use-company
-                        #'company-manual-begin
-                      #'notmuch-address-expand-name))))
-      (when use-company
+                      #'notmuch-address-expand-name)))
+      (when setup-company
        (notmuch-company-setup))
       (unless (memq pair message-completion-alist)
        (setq message-completion-alist
              (push pair message-completion-alist)))))
 
+(defun notmuch-address-toggle-internal-completion ()
+  "Toggle use of internal completion for current buffer.
+
+This overrides the global setting for address completion and
+toggles the setting in this buffer."
+  (interactive)
+  (if (local-variable-p 'notmuch-address-command)
+      (kill-local-variable 'notmuch-address-command)
+    (setq-local notmuch-address-command 'internal))
+  (if (boundp 'company-idle-delay)
+      (if (local-variable-p 'company-idle-delay)
+         (kill-local-variable 'company-idle-delay)
+       (setq-local company-idle-delay nil))))
+
 (defun notmuch-address-matching (substring)
   "Returns a list of completion candidates matching SUBSTRING.
 The candidates are taken from `notmuch-address-completions'."
@@ -115,7 +162,7 @@ external commands."
     (when (not notmuch-address-full-harvest-finished)
       ;; First, run quick synchronous harvest based on what the user
       ;; entered so far
-      (notmuch-address-harvest (format "to:%s*" original) t))
+      (notmuch-address-harvest original t))
     (prog1 (notmuch-address-matching original)
       ;; Then start the (potentially long-running) full asynchronous harvest if necessary
       (notmuch-address-harvest-trigger)))
@@ -123,7 +170,12 @@ external commands."
     (process-lines notmuch-address-command original))))
 
 (defun notmuch-address-expand-name ()
-  (when notmuch-address-command
+  (cond
+   ((and (eq notmuch-address-command 'internal)
+        notmuch-address-use-company
+        (bound-and-true-p company-mode))
+    (company-manual-begin))
+   (notmuch-address-command
     (let* ((end (point))
           (beg (save-excursion
                  (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
@@ -149,7 +201,8 @@ external commands."
            (delete-region beg end)
            (insert chosen))
        (message "No matches.")
-       (ding)))))
+       (ding))))
+   (t nil)))
 
 ;; Copied from `w3m-which-command'.
 (defun notmuch-address-locate-command (command)
@@ -191,32 +244,49 @@ external commands."
 
 The car is a partial harvest, and the cdr is a full harvest")
 
-(defun notmuch-address-harvest (&optional filter-query synchronous callback)
-  "Collect addresses completion candidates. It queries the
-notmuch database for all messages sent by the user optionally
-matching FILTER-QUERY (if not nil). It collects the destination
-addresses from those messages and stores them in
-`notmuch-address-completions'. Address harvesting may take some
-time so the address collection runs asynchronously unless
-SYNCHRONOUS is t. In case of asynchronous execution, CALLBACK is
-called when harvesting finishes."
-  (let* ((from-me-query (mapconcat (lambda (x) (concat "from:" x)) (notmuch-user-emails) " or "))
-        (query (if filter-query
-                   (format "(%s) and (%s)" from-me-query filter-query)
-                 from-me-query))
+(defun notmuch-address-harvest (&optional addr-prefix synchronous callback)
+  "Collect addresses completion candidates.
+
+It queries the notmuch database for messages sent/received (as
+configured with `notmuch-address-command`) by the user, collects
+destination/source addresses from those messages and stores them
+in `notmuch-address-completions'.
+
+If ADDR-PREFIX is not nil, only messages with to/from addresses
+matching ADDR-PREFIX*' are queried.
+
+Address harvesting may take some time so the address collection runs
+asynchronously unless SYNCHRONOUS is t. In case of asynchronous
+execution, CALLBACK is called when harvesting finishes."
+
+  (let* ((sent (eq (car notmuch-address-internal-completion) 'sent))
+        (config-query (cadr notmuch-address-internal-completion))
+        (prefix-query (when addr-prefix
+                        (format "%s:%s*" (if sent "to" "from") addr-prefix)))
+        (from-or-to-me-query
+         (mapconcat (lambda (x)
+                      (concat (if sent "from:" "to:") x))
+                    (notmuch-user-emails) " or "))
+        (query (if (or prefix-query config-query)
+                   (concat (format "(%s)" from-or-to-me-query)
+                           (when prefix-query
+                             (format " and (%s)" prefix-query))
+                           (when config-query
+                             (format " and (%s)" config-query)))
+                 from-or-to-me-query))
         (args `("address" "--format=sexp" "--format-version=2"
-                "--output=recipients"
+                ,(if sent "--output=recipients" "--output=sender")
                 "--deduplicate=address"
                 ,query)))
     (if synchronous
        (mapc #'notmuch-address-harvest-addr
                                   (apply 'notmuch-call-notmuch-sexp args))
       ;; Asynchronous
-      (let* ((current-proc (if filter-query
+      (let* ((current-proc (if addr-prefix
                               (car notmuch-address-harvest-procs)
                             (cdr notmuch-address-harvest-procs)))
             (proc-name (format "notmuch-address-%s-harvest"
-                               (if filter-query "partial" "full")))
+                               (if addr-prefix "partial" "full")))
             (proc-buf (concat " *" proc-name "*")))
        ;; Kill any existing process
        (when current-proc
@@ -228,7 +298,7 @@ called when harvesting finishes."
                     args))
        (set-process-filter current-proc 'notmuch-address-harvest-filter)
        (set-process-query-on-exit-flag current-proc nil)
-       (if filter-query
+       (if addr-prefix
            (setcar notmuch-address-harvest-procs current-proc)
          (setcdr notmuch-address-harvest-procs current-proc)))))
   ;; return value
@@ -249,6 +319,25 @@ called when harvesting finishes."
 
 ;;
 
+(defun notmuch-address-from-minibuffer (prompt)
+  (if (not notmuch-address-command)
+      (read-string prompt)
+    (let ((rmap (copy-keymap minibuffer-local-map))
+         (omap minibuffer-local-map))
+      ;; Configure TAB to start completion when executing read-string.
+      ;; "Original" minibuffer keymap is restored just before calling
+      ;; notmuch-address-expand-name as it may also use minibuffer-local-map
+      ;; (completing-read probably does not but if something else is used there).
+      (define-key rmap (kbd "TAB") (lambda ()
+                                    (interactive)
+                                    (let ((enable-recursive-minibuffers t)
+                                          (minibuffer-local-map omap))
+                                      (notmuch-address-expand-name))))
+      (let ((minibuffer-local-map rmap))
+       (read-string prompt)))))
+
+;;
+
 (provide 'notmuch-address)
 
 ;;; notmuch-address.el ends here
index b881d6dc9aa3f7a8c4f2b257507e8cdb397bec67..168315ff225fb68034979ad38cab1ebd976cde33 100644 (file)
@@ -16,7 +16,7 @@
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
 (defun notmuch-company-setup ()
   (company-mode)
   (make-local-variable 'company-backends)
-  (setq company-backends '(notmuch-company)))
+  (setq company-backends '(notmuch-company))
+  ;; Disable automatic company completion unless an internal
+  ;; completion method is configured. Company completion (using
+  ;; internal completion) can still be accessed via standard company
+  ;; functions, e.g., company-complete.
+  (unless (eq notmuch-address-command 'internal)
+    (setq-local company-idle-delay nil)))
 
 ;;;###autoload
 (defun notmuch-company (command &optional arg &rest _ignore)
@@ -72,7 +78,7 @@
                          (lambda (callback)
                            ;; First run quick asynchronous harvest based on what the user entered so far
                            (notmuch-address-harvest
-                            (format "to:%s*" arg) nil
+                            arg nil
                             (lambda (_proc _event)
                               (funcall callback (notmuch-address-matching arg))
                               ;; Then start the (potentially long-running) full asynchronous harvest if necessary
index 004463c316ea143bb6a2082ffb97a068ebc245db..e376aa8022d237caee6781eb29eb28e4475dd2bd 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Jameson Rollins <jrollins@finestructure.net>
 
index 9495c1a4874b589d003f9132d9e3c60c062933f7..75ccf579504b55c6e638fa4b66146ff3aaaaf3c4 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: David Edmondson <dme@dme.org>
 
@@ -265,7 +265,7 @@ International Bureau of Weights and Measures."
   :group 'notmuch-hello
   :group 'notmuch-hooks)
 
-(defvar notmuch-hello-url "http://notmuchmail.org"
+(defvar notmuch-hello-url "https://notmuchmail.org"
   "The `notmuch' web site.")
 
 (defvar notmuch-hello-custom-section-options
index fd770f1ec50e39f81afe481374fafbbc2f97b72a..963253c972656e2fe67e44b424e2397e69048da8 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Austin Clements <aclements@csail.mit.edu>
 ;;          David Edmondson <dme@dme.org>
index f05ded6f36df7941a9dea189382bdd250ff5e14d..2f015b0d29b0d007136b711354f503e9d86f1c62 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 
@@ -790,9 +790,15 @@ You may need to restart Emacs or upgrade your notmuch package."))
                    (insert-file-contents err-file)
                    (unless (eobp)
                      (buffer-string)))))
+          (command-string
+           (mapconcat (lambda (arg)
+                        (shell-quote-argument
+                         (cond ((stringp arg) arg)
+                               ((symbolp arg) (symbol-name arg))
+                               (t "*UNKNOWN ARGUMENT*"))))
+                      command " "))
           (extra
-           (concat
-            "command: " (mapconcat #'shell-quote-argument command " ") "\n"
+           (concat "command: " command-string "\n"
             (if (integerp exit-status)
                 (format "exit status: %s\n" exit-status)
               (format "exit signal: %s\n" exit-status))
index bbf61320d75dc4b9da974a801376f5c35ce1477c..1218c01e19e2e51a599c66f98014101187c2e300 100644 (file)
@@ -65,11 +65,15 @@ yet when sending a mail."
  :require 'notmuch-fcc-initialization
  :group 'notmuch-send)
 
-(defun notmuch-fcc-handler (destdir)
-  "Write buffer to `destdir', marking it as sent
+(defcustom notmuch-maildir-use-notmuch-insert 't
+  "Should fcc use notmuch insert instead of simple fcc"
+  :type '(choice :tag "Fcc Method"
+                (const :tag "Use notmuch insert" t)
+                (const :tag "Use simple fcc" nil))
+  :group 'notmuch-send)
 
-Intended to be dynamically bound to `message-fcc-handler-function'"
-    (notmuch-maildir-fcc-write-buffer-to-maildir destdir t))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions which set up the fcc header in the message buffer.
 
 (defun notmuch-fcc-header-setup ()
   "Add an Fcc header to the current message buffer.
@@ -110,27 +114,139 @@ by notmuch-mua-mail"
           (error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)")))))
 
     (when subdir
-      (message-add-header
-       (concat "Fcc: "
-              (file-truename
-               ;; If the resulting directory is not an absolute path,
-               ;; prepend the standard notmuch database path.
-               (if (= (elt subdir 0) ?/)
-                   subdir
-                 (concat (notmuch-database-path) "/" subdir)))))
-      
-      ;; finally test if fcc points to a valid maildir
-      (let ((fcc-header (message-field-value "Fcc")))
-       (unless (notmuch-maildir-fcc-dir-is-maildir-p fcc-header)
-         (cond ((not (file-writable-p fcc-header))
-                (error (format "No permission to create %s, which does not exist"
-                               fcc-header)))
-               ((y-or-n-p (format "%s is not a maildir. Create it? "
-                                  fcc-header))
-                (notmuch-maildir-fcc-create-maildir fcc-header))
-               (t
-                (error "Message not sent"))))))))
+      (if notmuch-maildir-use-notmuch-insert
+         (notmuch-maildir-add-notmuch-insert-style-fcc-header subdir)
+       (notmuch-maildir-add-file-style-fcc-header subdir)))))
+
+(defun notmuch-maildir-add-notmuch-insert-style-fcc-header (subdir)
+  ;; Notmuch insert does not accept absolute paths, so check the user
+  ;; really want this header inserted.
+
+  (when (or (not (= (elt subdir 0) ?/))
+           (y-or-n-p (format "Fcc header %s is an absolute path and notmuch insert is requested.\nInsert header anyway? "
+                             subdir)))
+    (message-add-header (concat "Fcc: " subdir))))
+
+(defun notmuch-maildir-add-file-style-fcc-header (subdir)
+  (message-add-header
+   (concat "Fcc: "
+          (file-truename
+           ;; If the resulting directory is not an absolute path,
+           ;; prepend the standard notmuch database path.
+           (if (= (elt subdir 0) ?/)
+               subdir
+             (concat (notmuch-database-path) "/" subdir))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions for saving a message either using notmuch insert or file
+;; fcc. First functions common to the two cases.
+
+(defmacro with-temporary-notmuch-message-buffer (&rest body)
+  "Set-up a temporary copy of the current message-mode buffer."
+  `(let ((case-fold-search t)
+        (buf (current-buffer))
+        (mml-externalize-attachments message-fcc-externalize-attachments))
+     (with-current-buffer (get-buffer-create " *message temp*")
+       (erase-buffer)
+       (insert-buffer-substring buf)
+       ,@body)))
+
+(defun notmuch-maildir-setup-message-for-saving ()
+  "Setup message for saving. Should be called on a temporary copy.
+
+This is taken from the function message-do-fcc."
+  (message-encode-message-body)
+  (save-restriction
+    (message-narrow-to-headers)
+    (let ((mail-parse-charset message-default-charset))
+      (mail-encode-encoded-word-buffer)))
+  (goto-char (point-min))
+  (when (re-search-forward
+        (concat "^" (regexp-quote mail-header-separator) "$")
+        nil t)
+    (replace-match "" t t )))
+
+(defun notmuch-maildir-message-do-fcc ()
+  "Process Fcc headers in the current buffer.
+
+This is a rearranged version of message mode's message-do-fcc."
+  (let (list file)
+    (save-excursion
+      (save-restriction
+       (message-narrow-to-headers)
+       (setq file (message-fetch-field "fcc" t)))
+      (when file
+       (with-temporary-notmuch-message-buffer
+        (save-restriction
+          (message-narrow-to-headers)
+          (while (setq file (message-fetch-field "fcc" t))
+            (push file list)
+            (message-remove-header "fcc" nil t)))
+        (notmuch-maildir-setup-message-for-saving)
+        ;; Process FCC operations.
+        (while list
+          (setq file (pop list))
+          (notmuch-fcc-handler file))
+        (kill-buffer (current-buffer)))))))
+
+(defun notmuch-fcc-handler (fcc-header)
+  "Store message with notmuch insert or normal (file) fcc.
+
+If `notmuch-maildir-use-notmuch-insert` is set then store the
+message using notmuch insert. Otherwise store the message using
+normal fcc."
+  (if notmuch-maildir-use-notmuch-insert
+      (notmuch-maildir-fcc-with-notmuch-insert fcc-header)
+    (notmuch-maildir-fcc-file-fcc fcc-header)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions for saving a message using notmuch insert.
+
+(defun notmuch-maildir-notmuch-insert-current-buffer (folder &optional create tags)
+  "Use notmuch insert to put the current buffer in the database.
+
+This inserts the current buffer as a message into the notmuch
+database in folder FOLDER. If CREATE is non-nil it will supply
+the --create-folder flag to create the folder if necessary. TAGS
+should be a list of tag changes to apply to the inserted message."
+  (let* ((args (append (when create (list "--create-folder"))
+                      (list (concat "--folder=" folder))
+                      tags)))
+    (apply 'notmuch-call-notmuch-process
+          :stdin-string (buffer-string) "insert" args)))
+
+(defun notmuch-maildir-fcc-with-notmuch-insert (fcc-header &optional create)
+  "Store message with notmuch insert.
+
+The fcc-header should be of the form \"folder +tag1 -tag2\" where
+folder is the folder (relative to the notmuch mailstore) to store
+the message in, and tag1 and tag2 are tag changes to apply to the
+stored message. If CREATE is non-nil then create the folder if
+necessary."
+  (let* ((args (split-string-and-unquote fcc-header))
+        (folder (car args))
+        (tags (cdr args)))
+    (condition-case nil
+       (notmuch-maildir-notmuch-insert-current-buffer folder create tags)
+      ;; Since there are many reasons notmuch insert could fail, e.g.,
+      ;; locked database, non-existent folder (which could be due to a
+      ;; typo, or just the user want a new folder, let the user decide
+      ;; how to deal with it.
+      (error
+       (let ((response (read-char-choice
+                       "Insert failed: (r)etry, (c)reate folder, (i)gnore, or  (e)dit the header? "
+                       '(?r ?c ?i ?e))))
+        (case response
+              (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header))
+              (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't))
+              (?i 't)
+              (?e (notmuch-maildir-fcc-with-notmuch-insert
+                   (read-from-minibuffer "Fcc header: " fcc-header)))))))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions for saving a message using file fcc.
+
 (defun notmuch-maildir-fcc-host-fixer (hostname)
   (replace-regexp-in-string "/\\|:"
                            (lambda (s)
@@ -192,6 +308,29 @@ if successful, nil if not."
    (concat destdir "/tmp/" msg-id)
    (concat destdir "/cur/" msg-id ":2," (when mark-seen "S"))))
 
+(defun notmuch-maildir-fcc-file-fcc (fcc-header)
+  "Write the message to the file specified by FCC-HEADER.
+
+It offers the user a chance to correct the header, or filesystem,
+if needed."
+  (if (notmuch-maildir-fcc-dir-is-maildir-p fcc-header)
+      (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header 't)
+    ;; The fcc-header is not a valid maildir see if the user wants to
+    ;; fix it in some way.
+    (let* ((prompt (format "Fcc %s is not a maildir: (r)etry, (c)reate folder, (i)gnore, or  (e)dit the header? "
+                          fcc-header))
+           (response (read-char-choice prompt '(?r ?c ?i ?e))))
+        (case response
+              (?r (notmuch-maildir-fcc-file-fcc fcc-header))
+              (?c (if (file-writable-p fcc-header)
+                      (notmuch-maildir-fcc-create-maildir fcc-header)
+                    (message "No permission to create %s." fcc-header)
+                    (sit-for 2))
+                  (notmuch-maildir-fcc-file-fcc fcc-header))
+              (?i 't)
+              (?e (notmuch-maildir-fcc-file-fcc
+                   (read-from-minibuffer "Fcc header: " fcc-header)))))))
+
 (defun notmuch-maildir-fcc-write-buffer-to-maildir (destdir &optional mark-seen)
   "Writes the current buffer to maildir destdir. If mark-seen is
 non-nil, it will write it to cur/, and mark it as read. It should
index d437b8574b925945169cb0b0f393690dcbff4c8e..55e4cfee98cee93cc2e6f06591f0bad81b7cc1ca 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Jesse Rosenthal <jrosenthal@jhu.edu>
 
index 399e1380df02a8ac2d0caae38179ad675135866f..fadf20fec7a7d6312ec8ad86d744b8743e9a129d 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: David Edmondson <dme@dme.org>
 
@@ -32,7 +32,7 @@
 
 (declare-function notmuch-show-insert-body "notmuch-show" (msg body depth))
 (declare-function notmuch-fcc-header-setup "notmuch-maildir-fcc" ())
-(declare-function notmuch-fcc-handler "notmuch-maildir-fcc" (destdir))
+(declare-function notmuch-maildir-message-do-fcc "notmuch-maildir-fcc" ())
 
 ;;
 
@@ -62,7 +62,7 @@ disabled: this would result in an incorrect behavior."))
                 (const :tag "Compose mail in a new window"  new-window)
                 (const :tag "Compose mail in a new frame"   new-frame)))
 
-(defcustom notmuch-mua-user-agent-function 'notmuch-mua-user-agent-full
+(defcustom notmuch-mua-user-agent-function nil
   "Function used to generate a `User-Agent:' string. If this is
 `nil' then no `User-Agent:' will be generated."
   :type '(choice (const :tag "No user agent string" nil)
@@ -73,7 +73,7 @@ disabled: this would result in an incorrect behavior."))
                           :value notmuch-mua-user-agent-full))
   :group 'notmuch-send)
 
-(defcustom notmuch-mua-hidden-headers '("^User-Agent:")
+(defcustom notmuch-mua-hidden-headers nil
   "Headers that are added to the `message-mode' hidden headers
 list."
   :type '(repeat string)
@@ -142,7 +142,7 @@ mutiple parts get a header."
   (let ((notmuch-version (if (string= notmuch-emacs-version "unknown")
                             (notmuch-cli-version)
                           notmuch-emacs-version)))
-    (concat "Notmuch/" notmuch-version " (http://notmuchmail.org)")))
+    (concat "Notmuch/" notmuch-version " (https://notmuchmail.org)")))
 
 (defun notmuch-mua-user-agent-emacs ()
   "Generate a `User-Agent:' string suitable for notmuch."
@@ -276,8 +276,7 @@ mutiple parts get a header."
 
 (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
   "Notmuch message composition mode. Mostly like `message-mode'"
-  (when notmuch-address-command
-    (notmuch-address-setup)))
+  (notmuch-address-setup))
 
 (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
 
@@ -334,7 +333,7 @@ modified. This function is notmuch addaptation of
          ;; C-h f compose-mail says that headers should be specified as
          ;; (string . value); however all the rest of message expects
          ;; headers to be symbols, not strings (eg message-header-format-alist).
-         ;; http://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html
+         ;; https://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html
          ;; We need to convert any string input, eg from rmail-start-mail.
          (dolist (h other-headers other-headers)
            (if (stringp (car h)) (setcar h (intern (capitalize (car h))))))))
@@ -490,13 +489,13 @@ will be addressed to all recipients of the source message."
 
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
-  (let ((message-fcc-handler-function #'notmuch-fcc-handler))
-    (message-send-and-exit arg)))
+  (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc))
+       (message-send-and-exit arg)))
 
 (defun notmuch-mua-send (&optional arg)
   (interactive "P")
-  (let ((message-fcc-handler-function #'notmuch-fcc-handler))
-    (message-send arg)))
+  (letf (((symbol-function 'message-do-fcc) #'notmuch-maildir-message-do-fcc))
+       (message-send arg)))
 
 (defun notmuch-mua-kill-buffer ()
   (interactive)
index 620ca89ddc5edb6e20623c9d691885ea0c0318b5..bb0379c102f653c2df12b08afd012186f3003a3e 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Austin Clements <aclements@csail.mit.edu>
 
index 480a0cfee487537c6197419f3b9645cafee24d2b..bca759fafaa373878971624a6b18e522ec368ae5 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: David Edmondson <dme@dme.org>
 
index 8587d8818cf759f4f3129e48072a8b1c98ea90f1..436ad160a05b8c0f9a698de1ba9fb025fc272912 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: David Bremner <david@tethera.net>
 
index 5d9b7b45c557b42c0ad540ebdc103b9e006d7394..6d3149bf45a99d594833e62719fb19eac43fb9d4 100644 (file)
@@ -16,7 +16,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 ;;          David Edmondson <dme@dme.org>
@@ -182,9 +182,9 @@ each attachment handler is logged in buffers with names beginning
 
 (defcustom notmuch-show-stash-mlarchive-link-alist
   '(("Gmane" . "http://mid.gmane.org/")
-    ("MARC" . "http://marc.info/?i=")
-    ("Mail Archive, The" . "http://mid.mail-archive.com/")
-    ("LKML" . "http://lkml.kernel.org/r/")
+    ("MARC" . "https://marc.info/?i=")
+    ("Mail Archive, The" . "https://mid.mail-archive.com/")
+    ("LKML" . "https://lkml.kernel.org/r/")
     ;; FIXME: can these services be searched by `Message-Id' ?
     ;; ("MarkMail" . "http://markmail.org/")
     ;; ("Nabble" . "http://nabble.com/")
@@ -1418,6 +1418,7 @@ reset based on the original query."
     (define-key map (kbd "TAB") 'notmuch-show-next-button)
     (define-key map "f" 'notmuch-show-forward-message)
     (define-key map "F" 'notmuch-show-forward-open-messages)
+    (define-key map "b" 'notmuch-show-resend-message)
     (define-key map "l" 'notmuch-show-filter-thread)
     (define-key map "r" 'notmuch-show-reply-sender)
     (define-key map "R" 'notmuch-show-reply)
@@ -1700,12 +1701,23 @@ user decision and we should not override it."
        (notmuch-show-mark-read)
        (notmuch-show-set-prop :seen t)))
 
+(defvar notmuch-show--seen-has-errored nil)
+(make-variable-buffer-local 'notmuch-show--seen-has-errored)
+
 (defun notmuch-show-command-hook ()
   (when (eq major-mode 'notmuch-show-mode)
     ;; We need to redisplay to get window-start and window-end correct.
     (redisplay)
     (save-excursion
-      (funcall notmuch-show-mark-read-function (window-start) (window-end)))))
+      (condition-case err
+         (funcall notmuch-show-mark-read-function (window-start) (window-end))
+       ((debug error)
+        (unless notmuch-show--seen-has-errored
+          (setq notmuch-show--seen-has-errored 't)
+          (setq header-line-format
+                (concat header-line-format
+                        (propertize "  [some mark read tag changes may have failed]"
+                                    'face font-lock-warning-face)))))))))
 
 (defun notmuch-show-filter-thread (query)
   "Filter or LIMIT the current thread based on a new query string.
@@ -1855,6 +1867,14 @@ any effects from previous calls to
       (error "No open messages to forward."))
     (notmuch-mua-new-forward-messages open-messages prompt-for-sender)))
 
+(defun notmuch-show-resend-message (addresses)
+  "Resend the current message."
+  (interactive (list (notmuch-address-from-minibuffer "Resend to: ")))
+  (when (y-or-n-p (concat "Confirm resend to " addresses " "))
+    (notmuch-show-view-raw-message)
+    (message-resend addresses)
+    (notmuch-bury-or-kill-this-buffer)))
+
 (defun notmuch-show-next-message (&optional pop-at-end)
   "Show the next message.
 
index 98064a3b6f64378679689840d435ffc10b6cf657..a3f0c52a72233934d992413653ba504d488922cf 100644 (file)
@@ -16,7 +16,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 ;;          Damien Cassou <damien.cassou@gmail.com>
index 4f9ca2deecf44e6f649898b32a4615946dcab6d9..52313199c50f039192b81500c37236d795afb9c8 100644 (file)
@@ -17,7 +17,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: David Edmondson <dme@dme.org>
 ;;          Mark Walters <markwalters1009@gmail.com>
index 88cc01ce1378f03f0c44afaeb00533075436181e..abf52f1772696461ff6fb559e37a5413f0c06e21 100644 (file)
@@ -16,7 +16,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
 ;;; Code:
 
index 065af16fb071baeb9cd53e1b43ffec85f2beca86..07fc1a1f6325363df0e0a55268899851dd3cc109 100644 (file)
@@ -16,7 +16,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 ;;          David Edmondson <dme@dme.org>
@@ -26,6 +26,7 @@
 (require 'coolj)
 
 (declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
+(defvar notmuch-show-indent-messages-width)
 
 ;;
 
@@ -335,12 +336,13 @@ message at the window width. When doing so, citation leaders in
 the wrapped text are maintained."
 
   (let* ((coolj-wrap-follows-window-size nil)
+        (indent (* depth notmuch-show-indent-messages-width))
         (limit (if (numberp notmuch-wash-wrap-lines-length)
                    (min notmuch-wash-wrap-lines-length
                         (window-width))
                  (window-width)))
         (fill-column (- limit
-                        depth
+                        indent
                         ;; 2 to avoid poor interaction with
                         ;; `word-wrap'.
                         2)))
index a8a4c8e56c2a16b9d9f2c303fa8831bc0232208d..43d56f7be499218214d3c5c64e346484e71ac18b 100644 (file)
@@ -15,7 +15,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 ;; Homepage: https://notmuchmail.org/
@@ -26,7 +26,7 @@
 ;;
 ;; You will first need to have the notmuch program installed and have a
 ;; notmuch database built in order to use this. See
-;; http://notmuchmail.org for details.
+;; https://notmuchmail.org for details.
 ;;
 ;; To install this software, copy it to a directory that is on the
 ;; `load-path' variable within emacs (a good candidate is
@@ -48,7 +48,7 @@
 ;;
 ;; Have fun, and let us know if you have any comment, questions, or
 ;; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
-;; required, but is available from http://notmuchmail.org).
+;; required, but is available from https://notmuchmail.org).
 
 ;;; Code:
 
@@ -311,6 +311,26 @@ there will be called at other points of notmuch execution."
   :group 'notmuch-search
   :group 'notmuch-faces)
 
+(defface notmuch-search-flagged-face
+  '((t
+     (:weight bold)))
+  "Face used in search mode face for flagged threads.
+
+This face is the default value for the \"flagged\" tag in
+`notmuch-search-line-faces`."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-unread-face
+  '((t
+     (:foreground "blue")))
+  "Face used in search mode for unread threads.
+
+This face is the default value for the \"unread\" tag in
+`notmuch-search-line-faces`."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
 (defun notmuch-search-mode ()
   "Major mode displaying results of a notmuch search.
 
@@ -654,9 +674,12 @@ of the result."
                  (goto-char (point-min))
                  (forward-line (1- notmuch-search-target-line)))))))))
 
-(defcustom notmuch-search-line-faces '(("unread" :weight bold)
-                                      ("flagged" :foreground "blue"))
-  "Tag/face mapping for line highlighting in notmuch-search.
+(defcustom notmuch-search-line-faces
+  '(("unread" 'notmuch-search-unread-face)
+    ("flagged" 'notmuch-search-flagged-face))
+  "Alist of tags to faces for line highlighting in notmuch-search.
+Each element looks like (TAG . FACE).
+A thread with TAG will have FACE applied.
 
 Here is an example of how to color search results based on tags.
  (the following text would be placed in your ~/.emacs file):
@@ -665,10 +688,12 @@ Here is an example of how to color search results based on tags.
                                    (\"deleted\" . (:foreground \"red\"
                                                  :background \"blue\"))))
 
-The attributes defined for matching tags are merged, with earlier
-attributes overriding later. A message having both \"deleted\"
-and \"unread\" tags with the above settings would have a green
-foreground and blue background."
+The FACE must be a face name (a symbol or string), a property
+list of face attributes, or a list of these.  The faces for
+matching tags are merged, with earlier attributes overriding
+later. A message having both \"deleted\" and \"unread\" tags with
+the above settings would have a green foreground and blue
+background."
   :type '(alist :key-type (string) :value-type (custom-face-edit))
   :group 'notmuch-search
   :group 'notmuch-faces)
diff --git a/hooks.c b/hooks.c
index 662629a951b309021cb86eebfbb0d0cca345e8d0..7348d322791f8eb23ec97e318f53123ba96d0084 100644 (file)
--- a/hooks.c
+++ b/hooks.c
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
index 3a070907271eb263627562087acac78f09e173bc..beb963582fe5053fff730fd7a225af4d47bc03dd 100644 (file)
@@ -39,6 +39,7 @@ libnotmuch_c_srcs =           \
        $(dir)/message-file.c   \
        $(dir)/messages.c       \
        $(dir)/sha1.c           \
+       $(dir)/built-with.c     \
        $(dir)/tags.c
 
 libnotmuch_cxx_srcs =          \
@@ -48,6 +49,8 @@ libnotmuch_cxx_srcs =         \
        $(dir)/index.cc         \
        $(dir)/message.cc       \
        $(dir)/query.cc         \
+       $(dir)/query-fp.cc      \
+       $(dir)/config.cc        \
        $(dir)/thread.cc
 
 libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
diff --git a/lib/built-with.c b/lib/built-with.c
new file mode 100644 (file)
index 0000000..2f1f0b5
--- /dev/null
@@ -0,0 +1,36 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright Â© 2016 David Bremner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch.h"
+#include "notmuch-private.h"
+
+notmuch_bool_t
+notmuch_built_with (const char *name)
+{
+    if (STRNCMP_LITERAL (name, "compact") == 0) {
+       return HAVE_XAPIAN_COMPACT;
+    } else if (STRNCMP_LITERAL (name, "field_processor") == 0) {
+       return HAVE_XAPIAN_FIELD_PROCESSOR;
+    } else if (STRNCMP_LITERAL (name, "retry_lock") == 0) {
+       return HAVE_XAPIAN_DB_RETRY_LOCK;
+    } else {
+       return FALSE;
+    }
+}
diff --git a/lib/config.cc b/lib/config.cc
new file mode 100644 (file)
index 0000000..0703b9b
--- /dev/null
@@ -0,0 +1,193 @@
+/* config.cc - API for database metadata
+ *
+ * Copyright Â© 2016 David Bremner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch.h"
+#include "notmuch-private.h"
+#include "database-private.h"
+
+static const std::string CONFIG_PREFIX = "C";
+
+struct _notmuch_config_list {
+    notmuch_database_t *notmuch;
+    Xapian::TermIterator iterator;
+    char *current_key;
+    char *current_val;
+};
+
+static int
+_notmuch_config_list_destroy (notmuch_config_list_t *list)
+{
+    /* invoke destructor w/o deallocating memory */
+    list->iterator.~TermIterator();
+    return 0;
+}
+
+notmuch_status_t
+notmuch_database_set_config (notmuch_database_t *notmuch,
+                            const char *key,
+                            const char *value)
+{
+    notmuch_status_t status;
+    Xapian::WritableDatabase *db;
+
+    status = _notmuch_database_ensure_writable (notmuch);
+    if (status)
+       return status;
+
+    try {
+       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+       db->set_metadata (CONFIG_PREFIX + key, value);
+    } catch (const Xapian::Error &error) {
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       notmuch->exception_reported = TRUE;
+       _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
+                              error.get_msg().c_str());
+    }
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_metadata_value (notmuch_database_t *notmuch,
+                const char *key,
+                std::string &value)
+{
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    try {
+       value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
+    } catch (const Xapian::Error &error) {
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       notmuch->exception_reported = TRUE;
+       _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
+                              error.get_msg().c_str());
+    }
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_get_config (notmuch_database_t *notmuch,
+                            const char *key,
+                            char **value)
+{
+    std::string strval;
+    notmuch_status_t status;
+
+    if (! value)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    status = _metadata_value (notmuch, key, strval);
+    if (status)
+       return status;
+
+    *value = strdup (strval.c_str ());
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_database_get_config_list (notmuch_database_t *notmuch,
+                                 const char *prefix,
+                                 notmuch_config_list_t **out)
+{
+    notmuch_config_list_t *list = NULL;
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    list = talloc (notmuch, notmuch_config_list_t);
+    if (! list) {
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    talloc_set_destructor (list, _notmuch_config_list_destroy);
+    list->notmuch = notmuch;
+    list->current_key = NULL;
+    list->current_val = NULL;
+
+    try {
+
+       new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin
+                                                    (CONFIG_PREFIX + (prefix ? prefix : "")));
+
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
+                              error.get_msg().c_str());
+       notmuch->exception_reported = TRUE;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    *out = list;
+
+  DONE:
+    if (status && list)
+       talloc_free (list);
+
+    return status;
+}
+
+notmuch_bool_t
+notmuch_config_list_valid (notmuch_config_list_t *metadata)
+{
+    if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
+       return FALSE;
+
+    return TRUE;
+}
+
+const char *
+notmuch_config_list_key (notmuch_config_list_t *list)
+{
+    if (list->current_key)
+       talloc_free (list->current_key);
+
+    list->current_key = talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
+
+    return list->current_key;
+}
+
+const char *
+notmuch_config_list_value (notmuch_config_list_t *list)
+{
+    std::string strval;
+    notmuch_status_t status;
+    const char *key = notmuch_config_list_key (list);
+
+    /* TODO: better error reporting?? */
+    status = _metadata_value (list->notmuch, key, strval);
+    if (status)
+       return NULL;
+
+    if (list->current_val)
+       talloc_free (list->current_val);
+
+    list->current_val = talloc_strdup (list, strval.c_str ());
+    return list->current_val;
+}
+
+void
+notmuch_config_list_move_to_next (notmuch_config_list_t *list)
+{
+    list->iterator++;
+}
+
+void
+notmuch_config_list_destroy (notmuch_config_list_t *list)
+{
+    talloc_free (list);
+}
index 3fb10f7a408415fc7b22a1ce7cf34fa72cbba5b7..ca71a92f6c26893d92cc1435d0713d4cc69e5d96 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -144,6 +144,13 @@ operator&=(_notmuch_features &a, _notmuch_features b)
     return a;
 }
 
+#define NOTMUCH_QUERY_PARSER_FLAGS (Xapian::QueryParser::FLAG_BOOLEAN | \
+                                   Xapian::QueryParser::FLAG_PHRASE | \
+                                   Xapian::QueryParser::FLAG_LOVEHATE | \
+                                   Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE | \
+                                   Xapian::QueryParser::FLAG_WILDCARD | \
+                                   Xapian::QueryParser::FLAG_PURE_NOT)
+
 struct _notmuch_database {
     notmuch_bool_t exception_reported;
 
@@ -176,6 +183,10 @@ struct _notmuch_database {
     Xapian::TermGenerator *term_gen;
     Xapian::ValueRangeProcessor *value_range_processor;
     Xapian::ValueRangeProcessor *date_range_processor;
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+    Xapian::FieldProcessor *date_field_processor;
+    Xapian::FieldProcessor *query_field_processor;
+#endif
     Xapian::ValueRangeProcessor *last_mod_range_processor;
 };
 
index c8c5e26106ad9d1086eb92ee0998d30a312dca99..5577aaf9f48ce5974d692721735122c7ff6ba5d3 100644 (file)
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
 
 #include "database-private.h"
 #include "parse-time-vrp.h"
+#include "query-fp.h"
 #include "string-util.h"
 
 #include <iostream>
@@ -48,6 +49,12 @@ typedef struct {
 #define STRINGIFY(s) _SUB_STRINGIFY(s)
 #define _SUB_STRINGIFY(s) #s
 
+#if HAVE_XAPIAN_DB_RETRY_LOCK
+#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
+#else
+#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
+#endif
+
 /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
  *
  * We currently have three different types of documents (mail, ghost,
@@ -184,6 +191,14 @@ typedef struct {
  *                     generated is 1 and the value will be
  *                     incremented for each thread ID.
  *
+ *     C*              metadata keys starting with C indicate
+ *                     configuration data. It can be managed with the
+ *                     n_database_*config* API.  There is a convention
+ *                     of hierarchical keys separated by '.' (e.g.
+ *                     query.notmuch stores the value for the named
+ *                     query 'notmuch'), but it is not enforced by the
+ *                     API.
+ *
  * Obsolete metadata
  * -----------------
  *
@@ -216,7 +231,7 @@ typedef struct {
 
 /* With these prefix values we follow the conventions published here:
  *
- * http://xapian.org/docs/omega/termprefixes.html
+ * https://xapian.org/docs/omega/termprefixes.html
  *
  * as much as makes sense. Note that I took some liberty in matching
  * the reserved prefix values to notmuch concepts, (for example, 'G'
@@ -245,10 +260,10 @@ static prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
     { "id",                    "Q" },
     { "path",                  "P" },
     /*
-     * Without the ":", since this is a multi-letter prefix, Xapian
-     * will add a colon itself if the first letter of the path is
-     * upper-case ASCII. Including the ":" forces there to always be a
-     * colon, which keeps our own logic simpler.
+     * Unconditionally add ':' to reduce potential ambiguity with
+     * overlapping prefixes and/or terms that start with capital
+     * letters. See Xapian document termprefixes.html for related
+     * discussion.
      */
     { "folder",                        "XFOLDER:" },
 };
@@ -368,6 +383,22 @@ _notmuch_database_log (notmuch_database_t *notmuch,
        talloc_free (notmuch->status_string);
 
     notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
+    va_end (va_args);
+}
+
+void
+_notmuch_database_log_append (notmuch_database_t *notmuch,
+                     const char *format,
+                     ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    if (notmuch->status_string)
+       notmuch->status_string = talloc_vasprintf_append (notmuch->status_string, format, va_args);
+    else
+       notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
 
     va_end (va_args);
 }
@@ -930,7 +961,7 @@ notmuch_database_open_verbose (const char *path,
 
        if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
            notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
-                                                              Xapian::DB_CREATE_OR_OPEN);
+                                                              DB_ACTION);
        } else {
            notmuch->xapian_db = new Xapian::Database (xapian_path);
        }
@@ -1000,6 +1031,14 @@ notmuch_database_open_verbose (const char *path,
        notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
        notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
        notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+       /* This currently relies on the query parser to pass anything
+        * with a .. to the range processor */
+       notmuch->date_field_processor = new DateFieldProcessor();
+       notmuch->query_parser->add_boolean_prefix("date", notmuch->date_field_processor);
+       notmuch->query_field_processor = new QueryFieldProcessor (*notmuch->query_parser, notmuch);
+       notmuch->query_parser->add_boolean_prefix("query", notmuch->query_field_processor);
+#endif
        notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
 
        notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
@@ -1090,6 +1129,13 @@ notmuch_database_close (notmuch_database_t *notmuch)
     delete notmuch->last_mod_range_processor;
     notmuch->last_mod_range_processor = NULL;
 
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+    delete notmuch->date_field_processor;
+    notmuch->date_field_processor = NULL;
+    delete notmuch->query_field_processor;
+    notmuch->query_field_processor = NULL;
+#endif
+
     return status;
 }
 
@@ -2168,8 +2214,8 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
      * References header, if available.  If not, fall back to the
      * first message ID in the In-Reply-To header. */
     if (last_ref_message_id) {
-        _notmuch_message_add_term (message, "replyto",
-                                   last_ref_message_id);
+       _notmuch_message_add_term (message, "replyto",
+                                  last_ref_message_id);
     } else if (in_reply_to_message_id) {
        _notmuch_message_add_term (message, "replyto",
                             in_reply_to_message_id);
@@ -2278,15 +2324,15 @@ _consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
     if (stored_id.empty ()) {
        return NULL;
     } else {
-        Xapian::WritableDatabase *db;
+       Xapian::WritableDatabase *db;
 
        db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
 
        /* Clear the metadata for this message ID. We don't need it
         * anymore. */
-        db->set_metadata (metadata_key, "");
+       db->set_metadata (metadata_key, "");
 
-        return talloc_strdup (ctx, stored_id.c_str ());
+       return talloc_strdup (ctx, stored_id.c_str ());
     }
 }
 
index 78637b3ad6236333b9d992ee9d1c7fe87e46f1ad..5de3319c47d1457f92da38f00717507e67684d9c 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -227,6 +227,9 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory,
                                   Xapian::sortable_serialise (mtime));
 
        db->replace_document (directory->document_id, directory->doc);
+
+       directory->mtime = mtime;
+
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
                 "A Xapian exception occurred setting directory mtime: %s.\n",
index 4f7c0d85b1c350d5977a1ddd00c5e0c01665f49d..63e737dd4edc2c93d254249f0d5dd807b44b008a 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 847700114ef8df1f634db307dd22423c655c90e8..5621f2a9fd85d35feaa7985898a2a67f5fe9ee51 100644 (file)
@@ -2,7 +2,7 @@ set -eu
 
 # we go through a bit of work to get the unmangled names of the
 # typeinfo symbols because of
-# http://sourceware.org/bugzilla/show_bug.cgi?id=10326
+# https://sourceware.org/bugzilla/show_bug.cgi?id=10326
 
 if [ $# -lt 2 ]; then
     echo Usage: $0 header obj1 obj2 obj3
@@ -17,7 +17,7 @@ nm  $* | awk '$1 ~ "^[0-9a-fA-F][0-9a-fA-F]*$" && $3 ~ "Xapian.*Error" {print $3
 while read sym; do
     demangled=$(c++filt $sym)
     case $demangled in
-       typeinfo*) 
+       typeinfo*)
            printf "\t$sym;\n"
            ;;
        *)
index f166aefd2fc1f6f6c5ea1e306e4fc033ead09c59..8c145540cbce0e285216470b59d47ef1a48efcde 100644 (file)
@@ -12,7 +12,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -144,7 +144,7 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres
        {9,  ' ',  ' ',  10, 0},
        {10, '\n', '\n', 11, 10},
        {11, 'M',  'M',  12, 0},
-       {12, ' ',  '`',  12, 11}  
+       {12, ' ',  '`',  12, 11}
     };
     int next;
 
index ee305202fffa63f0e1a1d024c738a1d69a39ebb4..db18b1630bfd0468fc967c684b33cb186b925a34 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 68393055b3eb73b73ee3a1a4b5bde38ce8d94c73..24e698ab11dfbff7c5fbed56cb26c47e2f7fc4ce 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -1444,7 +1444,7 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
 
     for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
        if ((strchr (combined_flags, flag2tag[i].flag) != NULL)
-           ^ 
+           ^
            flag2tag[i].inverse)
        {
            status = notmuch_message_add_tag (message, flag2tag[i].tag);
index 0eee5690d7feae1380aad512ede8110333d8f2b6..b5363bb8f6750d18efac91afdb9b5be0d6922670 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 92807975bb94562faefc949c19c080b9ad76618e..643d9dd9d68867b9fa05cba23b5f65b3ab62ebea 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -158,8 +158,8 @@ typedef enum _notmuch_private_status {
     ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS)\
      ?                                                                 \
      _internal_error (format " (%s).\n",                               \
-                      ##__VA_ARGS__,                                   \
-                      __location__),                                   \
+                     ##__VA_ARGS__,                                    \
+                     __location__),                                    \
      (notmuch_status_t) NOTMUCH_PRIVATE_STATUS_SUCCESS                 \
      :                                                                 \
      (notmuch_status_t) private_status)
@@ -196,6 +196,10 @@ void
 _notmuch_database_log (notmuch_database_t *notmuch,
                       const char *format, ...);
 
+void
+_notmuch_database_log_append (notmuch_database_t *notmuch,
+                             const char *format, ...);
+
 unsigned long
 _notmuch_database_new_revision (notmuch_database_t *notmuch);
 
@@ -477,11 +481,11 @@ _notmuch_mset_messages_move_to_next (notmuch_messages_t *messages);
 
 notmuch_bool_t
 _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
-                              unsigned int doc_id);
+                             unsigned int doc_id);
 
 void
 _notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
-                            unsigned int doc_id);
+                           unsigned int doc_id);
 
 /* querying xapian documents by type (e.g. "mail" or "ghost"): */
 notmuch_status_t
index 24944f0b84f201e9b811aff6f419aeca1e6e53bd..2faa146838c32e48214e50e3649a00c098442779 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -206,6 +206,7 @@ typedef struct _notmuch_message notmuch_message_t;
 typedef struct _notmuch_tags notmuch_tags_t;
 typedef struct _notmuch_directory notmuch_directory_t;
 typedef struct _notmuch_filenames notmuch_filenames_t;
+typedef struct _notmuch_config_list notmuch_config_list_t;
 #endif /* __DOXYGEN__ */
 
 /**
@@ -694,7 +695,7 @@ notmuch_database_get_all_tags (notmuch_database_t *db);
  * completely in the future, but it's likely to be a specialized
  * version of the general Xapian query syntax:
  *
- * http://xapian.org/docs/queryparser.html
+ * https://xapian.org/docs/queryparser.html
  *
  * As a special case, passing either a length-zero string, (that is ""),
  * or a string consisting of a single asterisk (that is "*"), will
@@ -1840,6 +1841,74 @@ notmuch_filenames_move_to_next (notmuch_filenames_t *filenames);
 void
 notmuch_filenames_destroy (notmuch_filenames_t *filenames);
 
+
+/**
+ * set config 'key' to 'value'
+ *
+ */
+notmuch_status_t
+notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value);
+
+/**
+ * retrieve config item 'key', assign to  'value'
+ *
+ * keys which have not been previously set with n_d_set_config will
+ * return an empty string.
+ *
+ * return value is allocated by malloc and should be freed by the
+ * caller.
+ */
+notmuch_status_t
+notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value);
+
+/**
+ * Create an iterator for all config items with keys matching a given prefix
+ */
+notmuch_status_t
+notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out);
+
+/**
+ * Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be called).
+ */
+notmuch_bool_t
+notmuch_config_list_valid (notmuch_config_list_t *config_list);
+
+/**
+ * return key for current config pair
+ *
+ * return value is owned by the iterator, and will be destroyed by the
+ * next call to notmuch_config_list_key or notmuch_config_list_destroy.
+ */
+const char *
+notmuch_config_list_key (notmuch_config_list_t *config_list);
+
+/**
+ * return 'value' for current config pair
+ *
+ * return value is owned by the iterator, and will be destroyed by the
+ * next call to notmuch_config_list_value or notmuch config_list_destroy
+ */
+const char *
+notmuch_config_list_value (notmuch_config_list_t *config_list);
+
+
+/**
+ * move 'config_list' iterator to the next pair
+ */
+void
+notmuch_config_list_move_to_next (notmuch_config_list_t *config_list);
+
+/**
+ * free any resources held by 'config_list'
+ */
+void
+notmuch_config_list_destroy (notmuch_config_list_t *config_list);
+
+/**
+ * interrogate the library for compile time features
+ */
+notmuch_bool_t
+notmuch_built_with (const char *name);
 /* @} */
 
 NOTMUCH_END_DECLS
index 03804cf50fa83e77a816ac040ca15992c1fb5db8..dd69149423072f396cbc096cfd10ab53c1e79087 100644 (file)
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
@@ -64,3 +64,24 @@ ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
 
     return valno;
 }
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+/* XXX TODO: is throwing an exception the right thing to do here? */
+Xapian::Query DateFieldProcessor::operator()(const std::string & str) {
+    time_t from, to, now;
+
+    /* Use the same 'now' for begin and end. */
+    if (time (&now) == (time_t) -1)
+       throw Xapian::QueryParserError("Unable to get current time");
+
+    if (parse_time_string (str.c_str (), &from, &now, PARSE_TIME_ROUND_DOWN))
+       throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+
+    if (parse_time_string (str.c_str (), &to, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
+       throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+
+    return Xapian::Query(Xapian::Query::OP_AND,
+                        Xapian::Query(Xapian::Query::OP_VALUE_GE, 0, Xapian::sortable_serialise ((double) from)),
+                        Xapian::Query(Xapian::Query::OP_VALUE_LE, 0, Xapian::sortable_serialise ((double) to)));
+}
+#endif
index 094c4f870f2784e2d3ed2979d8b5e06f07186993..c024dba2fed59035b4adcf57b0a5f04435efb3d7 100644 (file)
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
@@ -37,4 +37,9 @@ public:
     Xapian::valueno operator() (std::string &begin, std::string &end);
 };
 
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+class DateFieldProcessor : public Xapian::FieldProcessor {
+    Xapian::Query operator()(const std::string & str);
+};
+#endif
 #endif /* NOTMUCH_PARSE_TIME_VRP_H */
diff --git a/lib/query-fp.cc b/lib/query-fp.cc
new file mode 100644 (file)
index 0000000..c39f591
--- /dev/null
@@ -0,0 +1,43 @@
+/* query-fp.cc - "query:" field processor glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright Â© 2016 David Bremner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "database-private.h"
+#include "query-fp.h"
+#include <iostream>
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+
+Xapian::Query
+QueryFieldProcessor::operator() (const std::string & name)
+{
+    std::string key = "query." + name;
+    char *expansion;
+    notmuch_status_t status;
+
+    status = notmuch_database_get_config (notmuch, key.c_str (), &expansion);
+    if (status) {
+       throw Xapian::QueryParserError ("error looking up key" + name);
+    }
+
+    return parser.parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
+}
+#endif
diff --git a/lib/query-fp.h b/lib/query-fp.h
new file mode 100644 (file)
index 0000000..d6e4b31
--- /dev/null
@@ -0,0 +1,42 @@
+/* query-fp.h - query field processor glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright Â© 2016 David Bremner
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#ifndef NOTMUCH_QUERY_FP_H
+#define NOTMUCH_QUERY_FP_H
+
+#include <xapian.h>
+#include "notmuch.h"
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+class QueryFieldProcessor : public Xapian::FieldProcessor {
+ protected:
+    Xapian::QueryParser &parser;
+    notmuch_database_t *notmuch;
+
+ public:
+    QueryFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_)
+       : parser(parser_), notmuch(notmuch_) { };
+
+    Xapian::Query operator()(const std::string & str);
+};
+#endif
+#endif /* NOTMUCH_QUERY_FP_H */
index 77a7926ba80d6f13d1ca62ff7827bd18c9ef9c45..53efd4e18fc1698d88c0b8ac476fd33094f2e2fc 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -220,12 +220,6 @@ _notmuch_query_search_documents (notmuch_query_t *query,
        Xapian::Query string_query, final_query, exclude_query;
        Xapian::MSet mset;
        Xapian::MSetIterator iterator;
-       unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
-                             Xapian::QueryParser::FLAG_PHRASE |
-                             Xapian::QueryParser::FLAG_LOVEHATE |
-                             Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
-                             Xapian::QueryParser::FLAG_WILDCARD |
-                             Xapian::QueryParser::FLAG_PURE_NOT);
 
        if (strcmp (query_string, "") == 0 ||
            strcmp (query_string, "*") == 0)
@@ -233,7 +227,7 @@ _notmuch_query_search_documents (notmuch_query_t *query,
            final_query = mail_query;
        } else {
            string_query = notmuch->query_parser->
-               parse_query (query_string, flags);
+               parse_query (query_string, NOTMUCH_QUERY_PARSER_FLAGS);
            final_query = Xapian::Query (Xapian::Query::OP_AND,
                                         mail_query, string_query);
        }
@@ -282,7 +276,7 @@ _notmuch_query_search_documents (notmuch_query_t *query,
        case NOTMUCH_SORT_MESSAGE_ID:
            enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, FALSE);
            break;
-        case NOTMUCH_SORT_UNSORTED:
+       case NOTMUCH_SORT_UNSORTED:
            break;
        }
 
@@ -305,9 +299,10 @@ _notmuch_query_search_documents (notmuch_query_t *query,
 
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
-                              "A Xapian exception occurred performing query: %s\n"
+                              "A Xapian exception occurred performing query: %s\n",
+                              error.get_msg().c_str());
+       _notmuch_database_log_append (notmuch,
                               "Query string was: %s\n",
-                              error.get_msg().c_str(),
                               query->query_string);
 
        notmuch->exception_reported = TRUE;
@@ -418,7 +413,7 @@ _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
 
 void
 _notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
-                            unsigned int doc_id)
+                           unsigned int doc_id)
 {
     if (doc_id < doc_ids->bound)
        doc_ids->bitmap[DOCIDSET_WORD(doc_id)] &= ~(1 << DOCIDSET_BIT(doc_id));
@@ -579,12 +574,6 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
                                                   type));
        Xapian::Query string_query, final_query, exclude_query;
        Xapian::MSet mset;
-       unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
-                             Xapian::QueryParser::FLAG_PHRASE |
-                             Xapian::QueryParser::FLAG_LOVEHATE |
-                             Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
-                             Xapian::QueryParser::FLAG_WILDCARD |
-                             Xapian::QueryParser::FLAG_PURE_NOT);
 
        if (strcmp (query_string, "") == 0 ||
            strcmp (query_string, "*") == 0)
@@ -592,7 +581,7 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
            final_query = mail_query;
        } else {
            string_query = notmuch->query_parser->
-               parse_query (query_string, flags);
+               parse_query (query_string, NOTMUCH_QUERY_PARSER_FLAGS);
            final_query = Xapian::Query (Xapian::Query::OP_AND,
                                         mail_query, string_query);
        }
@@ -625,10 +614,11 @@ _notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsign
 
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch,
-                              "A Xapian exception occurred performing query: %s\n"
-                              "Query string was: %s\n",
-                              error.get_msg().c_str(),
-                              query->query_string);
+                              "A Xapian exception occurred performing query: %s\n",
+                              error.get_msg().c_str());
+       _notmuch_database_log_append (notmuch,
+                                     "Query string was: %s\n",
+                                     query->query_string);
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
index 94060d577233a4602b37a70b2343404c5dc28da3..b7dea1c2a577f4277455eaa0cfe9d2096aaab7c9 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -112,4 +112,3 @@ _notmuch_sha1_of_file (const char *filename)
 
     return result;
 }
-
index da72746ded494ff991a6f0f99608cd9da111cf62..43ebe4993e8f26db6441f46e7af5dc6b42886d0e 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  *         Austin Clements <aclements@csail.mit.edu>
index b7e5602cfbac9a33637f310c2c3498cc42851ef1..c7d3f66f491b5ec02495817ba85b6c6981c5fc6d 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 0c937d7697a1007cdb27bd5c18eb7d93dfc38ba8..84ee52980df6130948b7c2fd8c64bbb4c03d242f 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index e96e6639716aa017b1b36476900354d51563917b..c9b82330f6e08908e85fcae2891bb003c5dfec98 100644 (file)
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Authors: Carl Worth <cworth@cworth.org>
  *          Keith Packard <keithp@keithp.com>
index b3d0b668bc4d142726881c6eb8e471f181de3583..ebc092b844c91f5f488163cfb78f6eb77e6d453d 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -446,11 +446,19 @@ typedef enum dump_formats {
     DUMP_FORMAT_SUP
 } dump_format_t;
 
+typedef enum dump_includes {
+    DUMP_INCLUDE_TAGS = 1,
+    DUMP_INCLUDE_CONFIG = 2,
+} dump_include_t;
+
+#define NOTMUCH_DUMP_VERSION 2
+
 int
 notmuch_database_dump (notmuch_database_t *notmuch,
                       const char *output_file_name,
                       const char *query_str,
                       dump_format_t output_format,
+                      dump_include_t include,
                       notmuch_bool_t gzip_output);
 
 /* If status is non-zero (i.e. error) print appropriate
index 937372161df1abc95cc569da44a212367fe8a01f..855545d735769e1269bd5a08ce6c1e99856bb989 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Ben Gamari <bgamari.foss@gmail.com>
  */
index d252bb25d4bb565d78c88a14e5719949259a076d..e5d42a0cbfd505ed9f1ba5b6984251a7324a3061 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -27,7 +27,7 @@
 static const char toplevel_config_comment[] =
     " .notmuch-config - Configuration file for the notmuch mail system\n"
     "\n"
-    " For more information about notmuch, see http://notmuchmail.org";
+    " For more information about notmuch, see https://notmuchmail.org";
 
 static const char database_config_comment[] =
     " Database configuration\n"
@@ -750,6 +750,30 @@ _item_split (char *item, char **group, char **key)
     return 0;
 }
 
+#define BUILT_WITH_PREFIX "built_with."
+#define QUERY_PREFIX "query."
+
+static int
+_print_db_config(notmuch_config_t *config, const char *name)
+{
+    notmuch_database_t *notmuch;
+    char *val;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+       return EXIT_FAILURE;
+
+    /* XXX Handle UUID mismatch? */
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_get_config (notmuch, name, &val)))
+       return EXIT_FAILURE;
+
+     puts (val);
+
+    return EXIT_SUCCESS;
+}
+
 static int
 notmuch_config_command_get (notmuch_config_t *config, char *item)
 {
@@ -773,6 +797,11 @@ notmuch_config_command_get (notmuch_config_t *config, char *item)
        tags = notmuch_config_get_new_tags (config, &length);
        for (i = 0; i < length; i++)
            printf ("%s\n", tags[i]);
+    } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+       printf ("%s\n",
+               notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
+    } else if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
+       return _print_db_config (config, item);
     } else {
        char **value;
        size_t i, length;
@@ -799,11 +828,53 @@ notmuch_config_command_get (notmuch_config_t *config, char *item)
     return 0;
 }
 
+static int
+_set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
+{
+    notmuch_database_t *notmuch;
+    const char *val = "";
+
+    if (argc > 1) {
+       /* XXX handle lists? */
+       fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
+       return EXIT_FAILURE;
+    }
+
+    if (argc > 0) {
+       val = argv[0];
+    }
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    /* XXX Handle UUID mismatch? */
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_set_config (notmuch, key, val)))
+       return EXIT_FAILURE;
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_close (notmuch)))
+       return EXIT_FAILURE;
+
+    return EXIT_SUCCESS;
+}
+
 static int
 notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
 {
     char *group, *key;
 
+    if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+       fprintf (stderr, "Error: read only option: %s\n", item);
+       return 1;
+    }
+
+    if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
+       return _set_db_config (config, item, argc, argv);
+    }
+
     if (_item_split (item, &group, &key))
        return 1;
 
@@ -830,6 +901,46 @@ notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char
     return notmuch_config_save (config);
 }
 
+static
+void
+_notmuch_config_list_built_with ()
+{
+    printf("%scompact=%s\n",
+          BUILT_WITH_PREFIX,
+          notmuch_built_with ("compact") ? "true" : "false");
+    printf("%sfield_processor=%s\n",
+          BUILT_WITH_PREFIX,
+          notmuch_built_with ("field_processor") ? "true" : "false");
+    printf("%sretry_lock=%s\n",
+          BUILT_WITH_PREFIX,
+          notmuch_built_with ("retry_lock") ? "true" : "false");
+}
+
+static int
+_list_db_config (notmuch_config_t *config)
+{
+    notmuch_database_t *notmuch;
+    notmuch_config_list_t *list;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+       return EXIT_FAILURE;
+
+    /* XXX Handle UUID mismatch? */
+
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_get_config_list (notmuch, "", &list)))
+       return EXIT_FAILURE;
+
+    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+       printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+    }
+    notmuch_config_list_destroy (list);
+
+   return EXIT_SUCCESS;
+}
+
 static int
 notmuch_config_command_list (notmuch_config_t *config)
 {
@@ -865,7 +976,8 @@ notmuch_config_command_list (notmuch_config_t *config)
 
     g_strfreev (groups);
 
-    return 0;
+    _notmuch_config_list_built_with ();
+    return _list_db_config (config);
 }
 
 int
index 0b6e6f5443964f917d7577bc7719d578c253646c..35a2aa707b4d88e62a357b6e123a0e19e2decaa0 100644 (file)
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Keith Packard <keithp@keithp.com>
  */
index 829781f8b44feba2f76fb4e39427c5f1029b7fd1..cae1db8a99dcacb8b4a20cfa06bdfa599721a32f 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
 #include "string-util.h"
 #include <zlib.h>
 
+static int
+database_dump_config (notmuch_database_t *notmuch, gzFile output)
+{
+    notmuch_config_list_t *list;
+    int ret = EXIT_FAILURE;
+    char *buffer = NULL;
+    size_t buffer_size = 0;
+
+    if (print_status_database ("notmuch dump", notmuch,
+                              notmuch_database_get_config_list (notmuch, NULL, &list)))
+       goto DONE;
+
+    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+       if (hex_encode (notmuch, notmuch_config_list_key (list),
+                       &buffer, &buffer_size) != HEX_SUCCESS) {
+           fprintf (stderr, "Error: failed to hex-encode config key %s\n",
+                    notmuch_config_list_key (list));
+           goto DONE;
+       }
+       gzprintf (output, "#@ %s", buffer);
+
+       if (hex_encode (notmuch, notmuch_config_list_value (list),
+                       &buffer, &buffer_size) != HEX_SUCCESS) {
+           fprintf (stderr, "Error: failed to hex-encode config value %s\n",
+                    notmuch_config_list_value (list) );
+           goto DONE;
+       }
+
+       gzprintf (output, " %s\n", buffer);
+    }
+
+    ret = EXIT_SUCCESS;
+
+ DONE:
+    if (list)
+       notmuch_config_list_destroy (list);
+
+    if (buffer)
+       talloc_free (buffer);
+
+    return ret;
+}
+
+static void
+print_dump_header (gzFile output, int output_format, int include)
+{
+    gzprintf (output, "#notmuch-dump %s:%d %s%s%s\n",
+             (output_format == DUMP_FORMAT_SUP) ? "sup" : "batch-tag",
+             NOTMUCH_DUMP_VERSION,
+             (include & DUMP_INCLUDE_CONFIG) ? "config" : "",
+             (include & DUMP_INCLUDE_TAGS) && (include & DUMP_INCLUDE_CONFIG) ? "," : "",
+             (include & DUMP_INCLUDE_TAGS) ? "tags" : "");
+}
 
 static int
 database_dump_file (notmuch_database_t *notmuch, gzFile output,
-                   const char *query_str, int output_format)
+                   const char *query_str, int output_format, int include)
 {
     notmuch_query_t *query;
     notmuch_messages_t *messages;
     notmuch_message_t *message;
     notmuch_tags_t *tags;
 
+    print_dump_header (output, output_format, include);
+
+    if (include & DUMP_INCLUDE_CONFIG) {
+       if (print_status_database ("notmuch dump", notmuch,
+                                  database_dump_config(notmuch,output)))
+           return EXIT_FAILURE;
+    }
+
+    if (! (include & DUMP_INCLUDE_TAGS))
+       return EXIT_SUCCESS;
+
     if (! query_str)
        query_str = "";
 
@@ -130,6 +194,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
                       const char *output_file_name,
                       const char *query_str,
                       dump_format_t output_format,
+                      dump_include_t include,
                       notmuch_bool_t gzip_output)
 {
     gzFile output = NULL;
@@ -164,7 +229,7 @@ notmuch_database_dump (notmuch_database_t *notmuch,
        goto DONE;
     }
 
-    ret = database_dump_file (notmuch, output, query_str, output_format);
+    ret = database_dump_file (notmuch, output, query_str, output_format, include);
     if (ret) goto DONE;
 
     ret = gzflush (output, Z_FINISH);
@@ -226,6 +291,7 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
     int opt_index;
 
     int output_format = DUMP_FORMAT_BATCH_TAG;
+    int include = 0;
     notmuch_bool_t gzip_output = 0;
 
     notmuch_opt_desc_t options[] = {
@@ -233,6 +299,9 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
          (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
                                  { "batch-tag", DUMP_FORMAT_BATCH_TAG },
                                  { 0, 0 } } },
+       { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
+         (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
+                                 { "tags", DUMP_INCLUDE_TAGS} } },
        { NOTMUCH_OPT_STRING, &output_file_name, "output", 'o', 0  },
        { NOTMUCH_OPT_BOOLEAN, &gzip_output, "gzip", 'z', 0 },
        { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
@@ -245,6 +314,9 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_process_shared_options (argv[0]);
 
+    if (include == 0)
+       include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS;
+
     if (opt_index < argc) {
        query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
        if (query_str == NULL) {
@@ -254,7 +326,7 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
     }
 
     ret = notmuch_database_dump (notmuch, output_file_name, query_str,
-                                output_format, gzip_output);
+                                output_format, include, gzip_output);
 
     notmuch_database_destroy (notmuch);
 
index 4404cd7c33b870c9fa6c1a26c24143852b6e9c25..f9d83713b78958093e1fdd731596271ab3af3be4 100755 (executable)
@@ -15,7 +15,7 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see http://www.gnu.org/licenses/ .
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
 #
 # Authors: Jani Nikula <jani@nikula.org>
 #
@@ -39,8 +39,10 @@ USE_EMACSCLIENT=
 AUTO_DAEMON=
 CREATE_FRAME=
 
+escape -v pwd "$PWD"
+
 # The crux of it all: construct an elisp progn and eval it.
-ELISP="(prog1 'done (require 'notmuch) (notmuch-mua-new-mail)"
+ELISP="(prog1 'done (require 'notmuch) (cd \"$pwd\") (notmuch-mua-new-mail)"
 
 # Short options compatible with mutt(1).
 while getopts :s:c:b:i:h opt; do
@@ -95,7 +97,7 @@ while getopts :s:c:b:i:h opt; do
            ELISP="${ELISP} (message-goto-bcc) (insert \"${OPTARG}, \")"
            ;;
        --body|i)
-           ELISP="${ELISP} (message-goto-body) (cd \"${PWD}\") (insert-file \"${OPTARG}\")"
+           ELISP="${ELISP} (message-goto-body) (insert-file \"${OPTARG}\")"
            ;;
        --print)
            PRINT_ONLY=1
@@ -132,7 +134,7 @@ done
 
 # Kill the terminal/frame if we're creating one.
 if [ -z "$USE_EMACSCLIENT" -o -n "$CREATE_FRAME" -o -n "$NO_WINDOW" ]; then
-    ELISP="${ELISP} (setq message-exit-actions (list #'save-buffers-kill-terminal))"
+    ELISP="${ELISP} (message-add-action #'save-buffers-kill-terminal 'exit)"
 fi
 
 # End progn.
index 5205c17a290fb6cbea318d9cdfb76458953561c9..131f09e2c6cf61139319296a28d8d8d0df566167 100644 (file)
@@ -16,7 +16,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Peter Wang <novalazy@gmail.com>
  */
index 04cb5cac092a9cf08def0af2645f89a5d0394c5f..799fec2088489ee62882769a97a4097d22f08ae5 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -351,7 +351,6 @@ add_files (notmuch_database_t *notmuch,
           const char *path,
           add_files_state_t *state)
 {
-    DIR *dir = NULL;
     struct dirent *entry = NULL;
     char *next = NULL;
     time_t fs_mtime, db_mtime;
@@ -655,8 +654,6 @@ add_files (notmuch_database_t *notmuch,
   DONE:
     if (next)
        talloc_free (next);
-    if (dir)
-       closedir (dir);
     if (fs_entries) {
        for (i = 0; i < num_fs_entries; i++)
            free (fs_entries[i]);
@@ -1045,7 +1042,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
            }
 
            if (notmuch_database_dump (notmuch, backup_name, "",
-                                      DUMP_FORMAT_BATCH_TAG, TRUE)) {
+                                      DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS, TRUE)) {
                fprintf (stderr, "Backup failed. Aborting upgrade.");
                return EXIT_FAILURE;
            }
index 3c6d685cbd6054c68a0281e696e747c8045aff57..49513732e620d4867d1676878bd06d6124fb0062 100644 (file)
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Authors: Carl Worth <cworth@cworth.org>
  *         Keith Packard <keithp@keithp.com>
@@ -324,7 +324,7 @@ add_recipients_from_message (GMimeMessage *reply,
     unsigned int n = 0;
 
     /* Some mailing lists munge the Reply-To header despite it being A Bad
-     * Thing, see http://www.unicom.com/pw/reply-to-harmful.html
+     * Thing, see http://marc.merlins.org/netrants/reply-to-harmful.html
      *
      * The munging is easy to detect, because it results in a
      * redundant reply-to header, (with an address that already exists
@@ -664,7 +664,7 @@ notmuch_reply_format_sprinter(void *ctx,
        return 1;
 
     if (count != 1) {
-       fprintf (stderr, "Error: search term did not match precisely one message.\n");
+       fprintf (stderr, "Error: search term did not match precisely one message (matched %d messages).\n", count);
        return 1;
     }
 
index 9abc64fdd89e6ae7c61e84bc3d3f7f269e72c8de..371237c5d6270f388834b55c161c7af753b55a0c 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
 #include "string-util.h"
 #include "zlib-extra.h"
 
+static int
+process_config_line (notmuch_database_t *notmuch, const char* line)
+{
+    const char *key_p, *val_p;
+    char *key, *val;
+    size_t key_len,val_len;
+    const char *delim = " \t\n";
+    int ret = EXIT_FAILURE;
+
+    void *local = talloc_new(NULL);
+
+    key_p = strtok_len_c (line, delim, &key_len);
+    val_p = strtok_len_c (key_p+key_len, delim, &val_len);
+
+    key = talloc_strndup (local, key_p, key_len);
+    val = talloc_strndup (local, val_p, val_len);
+    if (hex_decode_inplace (key) != HEX_SUCCESS ||
+       hex_decode_inplace (val) != HEX_SUCCESS ) {
+       fprintf (stderr, "hex decoding failure on line %s\n", line);
+       goto DONE;
+    }
+
+    if (print_status_database ("notmuch restore", notmuch,
+                              notmuch_database_set_config (notmuch, key, val)))
+       goto DONE;
+
+    ret = EXIT_SUCCESS;
+
+ DONE:
+    talloc_free (local);
+    return ret;
+}
+
 static regex_t regex;
 
 /* Non-zero return indicates an error in retrieving the message,
@@ -137,6 +170,7 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
 
     int ret = 0;
     int opt_index;
+    int include = 0;
     int input_format = DUMP_FORMAT_AUTO;
 
     if (notmuch_database_open (notmuch_config_get_database_path (config),
@@ -152,6 +186,10 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
                                  { "batch-tag", DUMP_FORMAT_BATCH_TAG },
                                  { "sup", DUMP_FORMAT_SUP },
                                  { 0, 0 } } },
+       { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
+         (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
+                                 { "tags", DUMP_INCLUDE_TAGS} } },
+
        { NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
        { NOTMUCH_OPT_BOOLEAN,  &accumulate, "accumulate", 'a', 0 },
        { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
@@ -167,6 +205,10 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
     notmuch_process_shared_options (argv[0]);
     notmuch_exit_if_unmatched_db_uuid (notmuch);
 
+    if (include == 0) {
+       include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS;
+    }
+
     name_for_error = input_file_name ? input_file_name : "stdin";
 
     if (! accumulate)
@@ -225,11 +267,23 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
            ret = EXIT_FAILURE;
            goto DONE;
        }
+
+       if ((include & DUMP_INCLUDE_CONFIG) && line_len >= 2 && line[0] == '#' && line[1] == '@') {
+           ret = process_config_line(notmuch, line+2);
+           if (ret)
+               goto DONE;
+       }
+
     } while ((line_len == 0) ||
             (line[0] == '#') ||
             /* the cast is safe because we checked about for line_len < 0 */
             (strspn (line, " \t\n") == (unsigned)line_len));
 
+    if (! (include & DUMP_INCLUDE_TAGS)) {
+       ret = EXIT_SUCCESS;
+       goto DONE;
+    }
+
     char *p;
     for (p = line; (input_format == DUMP_FORMAT_AUTO) && *p; p++) {
        if (*p == '(')
index 6d08c250cf93ee81981adcf34608ebba9a723805..8c65d5ad196d7705fca8142eac5199bae9c658b2 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 9aaf9286e8b257bd4946c7f81804b80117d5e3ca..9a66810db385cbbb9e64f93d7af4f1953546be44 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 87e52bbc0e81a7f8b0c4c7ca5c374cfdbb5da068..22fa655ad20d8deea55297af4616c2cec7e292d0 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
@@ -904,7 +904,7 @@ do_show_single (void *ctx,
        return 1;
 
     if (count != 1) {
-       fprintf (stderr, "Error: search term did not match precisely one message.\n");
+       fprintf (stderr, "Error: search term did not match precisely one message (matched %d messages).\n", count);
        return 1;
     }
 
index 0d1532826a2e81999ba833d4b18fa800fc060d81..18d78ddd1ad7a2391e9f66b7f5c6c2eb905c42a1 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index e250c3d5ea5d5af574e2a8a837896e02e1499739..2734b36a566e5a9fe63cd9ff0d1246405116abca 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index ce6c5756c9700af25fa79d3e3f5379b7b9122e2c..38b73c1d2accea68fa9d667fccfe518f97ab86ea 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -14,7 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Authors: Carl Worth <cworth@cworth.org>
  *         Keith Packard <keithp@keithp.com>
@@ -354,7 +354,7 @@ notmuch_command (notmuch_config_t *config,
            "You can also use \"notmuch show\" with any of the thread IDs resulting\n"
            "from a search. Finally, you may want to explore using a more sophisticated\n"
            "interface to notmuch such as the emacs interface implemented in notmuch.el\n"
-           "or any other interface described at http://notmuchmail.org\n\n"
+           "or any other interface described at https://notmuchmail.org\n\n"
            "And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
            "Have fun, and may your inbox never have much mail.\n\n",
            notmuch_config_get_user_name (config),
index 79994c9ae6234a19ef29af4247c303e8e8f2e0f3..5146edd3396d9d91ea5ad2d7a8a77611e85aa3b9 100644 (file)
@@ -21,9 +21,9 @@ Summary:        Thread-based email index, search and tagging
 
 Group:          Applications/Internet
 License:        GPLv3+
-URL:            http://notmuchmail.org/
+URL:            https://notmuchmail.org/
 
-Source0:        http://notmuchmail.org/releases/notmuch-%{version}.tar.gz
+Source0:        https://notmuchmail.org/releases/notmuch-%{version}.tar.gz
 
 BuildRequires:  xapian-core-devel gmime-devel libtalloc-devel
 BuildRequires:  zlib-devel emacs-el emacs-nox python ruby ruby-devel perl
index 1cef47d4b0bd85faafe8fdd1cff5ad368d56459c..48ec5b0c1843394aac2226bd58f62c7219033665 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
index bfa4ee3560f62241644c45459a40fafb674f6944..2063bcadb3faf8d21cd49787b44ccbec1af88e4c 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
index 3469aa3d6aadc67973efc8fd58589059cd875fb5..9dc260e3dc51f752105470ff6bb64d40b9aa6cd9 100644 (file)
@@ -10,7 +10,7 @@ MEMORY_TEST_SCRIPT := ${dir}/notmuch-memory-test
 CORPUS_NAME := notmuch-email-corpus-$(PERFTEST_VERSION).tar.xz
 TXZFILE := ${dir}/download/${CORPUS_NAME}
 SIGFILE := ${TXZFILE}.asc
-DEFAULT_URL :=  http://notmuchmail.org/releases/${CORPUS_NAME}
+DEFAULT_URL :=  https://notmuchmail.org/releases/${CORPUS_NAME}
 
 perf-test: time-test memory-test
 
@@ -32,7 +32,7 @@ setup-perf-test: $(TXZFILE)
 $(TXZFILE):
        @printf "\nPlease download ${TXZFILE} using:\n\n"
        @printf "\t%% make download-corpus\n\n"
-       @echo or see http://notmuchmail.org/corpus for download locations
+       @echo or see https://notmuchmail.org/corpus for download locations
        @echo
        @false
 
index 996724cdf9472b0cb110446402d97e199668ca0e..fbc61028d2db4dfdc204cd4a78e2613cadd78c3c 100644 (file)
@@ -37,7 +37,7 @@ To fetch the actual corpus it should work to run
 
 In case that fails or is too slow, check
 
-   http://notmuchmail.org/corpus
+   https://notmuchmail.org/corpus
 
 for a list of mirrors.
 
index 3cf28c7f5b78ea6385b76642a9eee9b06789b175..047aac70724601dd0c84e2c1df9d5cbe064960a7 100755 (executable)
@@ -3,6 +3,7 @@
 # Run tests
 #
 # Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
 #
 # Adapted from a Makefile to a shell script by Carl Worth (2010)
 
@@ -14,7 +15,7 @@ if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
     exit 1
 fi
 
-cd $(dirname "$0")
+cd "$(dirname "$0")"
 
 for test in M*.sh; do
     ./"$test" "$@"
index 7113efbfd5649576e83d7720b2d06766cd12c23d..4dd21fe1c5726accb7951ff991bd74afdb8644d4 100755 (executable)
@@ -3,6 +3,7 @@
 # Run tests
 #
 # Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
 #
 # Adapted from a Makefile to a shell script by Carl Worth (2010)
 
@@ -14,7 +15,7 @@ if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
     exit 1
 fi
 
-cd $(dirname "$0")
+cd "$(dirname "$0")"
 
 for test in T*.sh; do
     ./"$test" "$@"
index 653651236c9b8d55a2ffd820fea597e446e6eba7..cc8b27de8007555020ed72b16887648306c667c5 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 0aa51e8b554ff527dc8826b9904c952a6febcbfe..08783e11d3bd37ddef3a02fdb606164008edfa89 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Peter Feigl <peter.feigl@gmx.at>
  */
index 022f2cf7af0bfa3b9bec1b64505c49a651434db5..91b369367bdf0814706815ddd9a1698bf170b651 100644 (file)
@@ -19,7 +19,7 @@ $(dir)/hex-xcode: $(dir)/hex-xcode.o command-line-arguments.o util/libutil.a
        $(call quiet,CC) $^ -o $@ $(LDFLAGS) $(TALLOC_LDFLAGS)
 
 random_corpus_deps =  $(dir)/random-corpus.o  $(dir)/database-test.o \
-                       notmuch-config.o command-line-arguments.o \
+                       notmuch-config.o status.o command-line-arguments.o \
                        lib/libnotmuch.a util/libutil.a \
                        parse-time-string/libparse-time-string.a
 
index bd9ab5470c7c499c39d41fa858faebe733e180b7..104a120ea28bfb7250243f1d331ed8efd1e99fa0 100644 (file)
@@ -145,13 +145,9 @@ Tddd-testname.sh where 'ddd' is three digits and 'testname' the "bare"
 name of your test. Tests will be run in order the 'ddd' part determines.
 
 The test script should start with the standard "#!/usr/bin/env bash"
-with copyright notices, and an assignment to variable 'test_description',
-like this:
+and an assignment to variable 'test_description', like this:
 
        #!/usr/bin/env bash
-       #
-       # Copyright (c) 2005 Junio C Hamano
-       #
 
        test_description='xxx test (option --frotz)
 
index f404908a4a1c71f3f65e92c375d988604ffa469c..0915abdba3a03838e552cf91deb6d32d9ede1c65 100755 (executable)
@@ -43,10 +43,10 @@ notmuch config set foo.nonexistent
 test_expect_equal "$(notmuch config get foo.nonexistent)" ""
 
 test_begin_subtest "List all items"
-notmuch config set database.path "/canonical/path"
-output=$(notmuch config list)
-test_expect_equal "$output" "\
-database.path=/canonical/path
+notmuch config list 2>&1 | notmuch_config_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+Error opening database at MAIL_DIR/.notmuch: No such file or directory
+database.path=MAIL_DIR
 user.name=Notmuch Test Suite
 user.primary_email=test_suite@notmuchmail.org
 user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
@@ -56,7 +56,12 @@ search.exclude_tags=
 maildir.synchronize_flags=true
 crypto.gpg_path=gpg
 foo.string=this is another string value
-foo.list=this;is another;list value;"
+foo.list=this;is another;list value;
+built_with.compact=something
+built_with.field_processor=something
+built_with.retry_lock=something
+EOF
+test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Top level --config=FILE option"
 cp "${NOTMUCH_CONFIG}" alt-config
index cf0c00bc5c1fcd3900754db00cee34cf8be7c428..021f2d0bea7c10de782ffcdd2a5724ba172fc202 100755 (executable)
@@ -19,7 +19,7 @@ another.suite@example.com
 foo bar
 baz
 EOF
-output=$(notmuch --config=new-notmuch-config config list)
+output=$(notmuch --config=new-notmuch-config config list | notmuch_built_with_sanitize)
 test_expect_equal "$output" "\
 database.path=/path/to/maildir
 user.name=Test Suite
@@ -29,6 +29,9 @@ new.tags=foo;bar;
 new.ignore=
 search.exclude_tags=baz;
 maildir.synchronize_flags=true
-crypto.gpg_path=gpg"
+crypto.gpg_path=gpg
+built_with.compact=something
+built_with.field_processor=something
+built_with.retry_lock=something"
 
 test_done
index a451ffaeb39e3db7529332564b214c8d00f90b8a..61d1311608a755c5815bf545ff46170036339fa5 100755 (executable)
@@ -188,7 +188,7 @@ cat <<EOF > EXPECTED
 +%22%27%22%27%22%22%27%27 +inbox +tag4 +tag5 +unread -- id:msg-002@notmuch-test-suite
 EOF
 
-notmuch dump --format=batch-tag | sort > OUTPUT
+NOTMUCH_DUMP_TAGS > OUTPUT
 notmuch restore --format=batch-tag < BACKUP
 test_expect_equal_file EXPECTED OUTPUT
 
@@ -209,7 +209,7 @@ cat <<EOF > EXPECTED
 +%21@%23%20%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e +inbox +tag5 +unread -- id:msg-001@notmuch-test-suite
 EOF
 
-notmuch dump --format=batch-tag | sort > OUTPUT
+NOTMUCH_DUMP_TAGS > OUTPUT
 notmuch restore --format=batch-tag < BACKUP
 test_expect_equal_file EXPECTED OUTPUT
 
@@ -235,7 +235,7 @@ cat <<EOF > EXPECTED
 +%2a@%7d%cf%b5%f4%85%80%adO3%da%a7 +=%e0%ac%95%c8%b3+%ef%aa%95%c8%a64w%c7%9d%c9%a2%cf%b3%d6%82%24B%c4%a9%c5%a1UX%ee%99%b0%27E7%ca%a4%d0%8b%5d +A%e1%a0%bc%de%8b%d5%b2V%d9%9b%f3%b5%a2%a3M%d8%a1u@%f0%a0%ac%948%7e%f0%ab%86%af%27 +L%df%85%ef%a1%a5m@%d3%96%c2%ab%d4%9f%ca%b8%f3%b3%a2%bf%c7%b1_u%d7%b4%c7%b1 +P%c4%98%2f +R +inbox +tag5 +unread +%7e%d1%8b%25%ec%a0%ae%d1%a0M%3b%e3%b6%b7%e9%a4%87%3c%db%9a%cc%a8%e1%96%9d +%c4%bf7%c7%ab9H%c4%99k%ea%91%bd%c3%8ck%e2%b3%8dk%c5%952V%e4%99%b2%d9%b3%e4%8b%bda%5b%24%c7%9b +%da%88=f%cc%b9I%ce%af%7b%c9%97%e3%b9%8bH%cb%92X%d2%8c6 +%dc%9crh%d2%86B%e5%97%a2%22t%ed%99%82d -- id:msg-001@notmuch-test-suite
 EOF
 
-notmuch dump --format=batch-tag | sort > OUTPUT
+NOTMUCH_DUMP_TAGS > OUTPUT
 notmuch restore --format=batch-tag < BACKUP
 test_expect_equal_file EXPECTED OUTPUT
 
@@ -260,7 +260,7 @@ cat <<EOF > EXPECTED
 +foo%3a%3abar%25 +found%3a%3ait +inbox +tag5 +unread +winner -- id:msg-001@notmuch-test-suite
 EOF
 
-notmuch dump --format=batch-tag | sort > OUTPUT
+NOTMUCH_DUMP_TAGS > OUTPUT
 notmuch restore --format=batch-tag < BACKUP
 test_expect_equal_file EXPECTED OUTPUT
 
index dfea2d19caa93fece9fdbd3dc0ba0fbefd94c828..832a4ad311b708e5b8f86dd2df92d7958fd6c96b 100755 (executable)
@@ -8,7 +8,7 @@ add_message
 
 test_begin_subtest "Attempt to show multiple raw messages"
 output=$(notmuch show --format=raw "*" 2>&1)
-test_expect_equal "$output" "Error: search term did not match precisely one message."
+test_expect_equal "$output" "Error: search term did not match precisely one message (matched 2 messages)."
 
 test_begin_subtest "Show a raw message"
 output=$(notmuch show --format=raw id:msg-001@notmuch-test-suite | notmuch_date_sanitize)
index 97e9e7f984a362e2c448f916151ce0c66d2d4994..faa10364acf52a19a9553ebe515b4bb01bd81b61 100755 (executable)
@@ -97,7 +97,7 @@ test_expect_equal_file dump.expected dump.actual
 # Note, we assume all messages from cworth have a message-id
 # containing cworth.org
 
-grep 'cworth[.]org' dump.expected > dump-cworth.expected
+{ head -1 dump.expected ; grep 'cworth[.]org' dump.expected; } > dump-cworth.expected
 
 test_begin_subtest "dump -- from:cworth"
 notmuch dump -- from:cworth > dump-dash-cworth.actual
@@ -118,16 +118,16 @@ notmuch search --output=messages from:cworth | sed s/^id:// |\
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "format=batch-tag, dump sanity check."
-notmuch dump --format=sup from:cworth | cut -f1 -d' ' | \
+NOTMUCH_DUMP_TAGS --format=sup from:cworth | cut -f1 -d' ' | \
     sort > EXPECTED.$test_count
-notmuch dump --format=batch-tag from:cworth | sed 's/^.*-- id://' | \
+NOTMUCH_DUMP_TAGS --format=batch-tag from:cworth | sed 's/^.*-- id://' | \
     sort > OUTPUT.$test_count
 test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
 
 test_begin_subtest "format=batch-tag, missing newline"
 printf "+a_tag_without_newline -- id:20091117232137.GA7669@griffis1.net" > IN
 notmuch restore --accumulate < IN
-notmuch dump id:20091117232137.GA7669@griffis1.net > OUT
+NOTMUCH_DUMP_TAGS id:20091117232137.GA7669@griffis1.net > OUT
 cat <<EOF > EXPECTED
 +a_tag_without_newline +inbox +unread -- id:20091117232137.GA7669@griffis1.net
 EOF
@@ -156,7 +156,7 @@ cat <<EOF >EXPECTED.$test_count
 + -- id:20091117232137.GA7669@griffis1.net
 EOF
 notmuch restore --format=batch-tag < EXPECTED.$test_count
-notmuch dump --format=batch-tag id:20091117232137.GA7669@griffis1.net > OUTPUT.$test_count
+NOTMUCH_DUMP_TAGS --format=batch-tag id:20091117232137.GA7669@griffis1.net > OUTPUT.$test_count
 test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
 
 tag1='comic_swear=$&^%$^%\\//-+$^%$'
@@ -219,9 +219,9 @@ notmuch dump --format=batch-tag > OUTPUT.$test_count
 test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
 
 test_begin_subtest 'format=batch-tag, checking encoded output'
-notmuch dump --format=batch-tag -- from:cworth |\
+NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
         awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
-notmuch dump --format=batch-tag -- from:cworth  > OUTPUT.$test_count
+NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth  > OUTPUT.$test_count
 test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
 
 test_begin_subtest 'restoring sane tags'
index daa02568f5f988f6638753fd20887b4b66a9e8ba..202fc3bfcfc34abda008e95b96228d7cbc3156f6 100755 (executable)
@@ -193,7 +193,6 @@ emacs_deliver_message \
      (kill-whole-line)
      (insert "To: user@example.com\n")'
 sed \
-    -e s',^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' \
     -e s',^Message-ID: <.*>$,Message-ID: <XXX>,' \
     -e s',^\(Content-Type: text/plain\); charset=us-ascii$,\1,' < sent_message >OUTPUT
 cat <<EOF >EXPECTED
@@ -201,7 +200,6 @@ From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: user@example.com
 Subject: Testing message sent via SMTP
 Date: 01 Jan 2000 12:00:00 -0000
-User-Agent: Notmuch/XXX Emacs/XXX
 Message-ID: <XXX>
 MIME-Version: 1.0
 Content-Type: text/plain
@@ -310,7 +308,6 @@ test_emacs '(let ((message-hidden-headers ''()))
            (test-output))'
 sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: <XXX>/' OUTPUT
 sed -i -e 's/^References: <.*>$/References: <XXX>/' OUTPUT
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: user@example.com
@@ -318,7 +315,6 @@ Subject: Re: Testing message sent via SMTP
 In-Reply-To: <XXX>
 Fcc: ${MAIL_DIR}/sent
 References: <XXX>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Notmuch Test Suite <test_suite@notmuchmail.org> writes:
 
@@ -335,7 +331,6 @@ test_emacs "(let ((message-hidden-headers '()))
            (notmuch-test-wait)
            (notmuch-search-reply-to-thread)
            (test-output))"
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
 To: Sender <sender@example.com>
@@ -343,7 +338,6 @@ Subject: Re: ${test_subtest_name}
 In-Reply-To: <${gen_msg_id}>
 Fcc: ${MAIL_DIR}/sent
 References: <${gen_msg_id}>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Sender <sender@example.com> writes:
 
@@ -361,7 +355,6 @@ test_emacs "(let ((message-hidden-headers '()))
            (notmuch-test-wait)
            (notmuch-search-reply-to-thread)
            (test-output))"
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: Sender <sender@example.com>, someone@example.com
@@ -369,7 +362,6 @@ Subject: Re: ${test_subtest_name}
 In-Reply-To: <${gen_msg_id}>
 Fcc: ${MAIL_DIR}/sent
 References: <${gen_msg_id}>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Sender <sender@example.com> writes:
 
@@ -382,7 +374,6 @@ test_emacs '(let ((message-hidden-headers ''()))
            (notmuch-show "id:20091118002059.067214ed@hikari")
                (notmuch-show-reply)
                (test-output))'
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: Adrian Perez de Castro <aperez@igalia.com>, notmuch@notmuchmail.org
@@ -390,7 +381,6 @@ Subject: Re: [notmuch] Introducing myself
 In-Reply-To: <20091118002059.067214ed@hikari>
 Fcc: ${MAIL_DIR}/sent
 References: <20091118002059.067214ed@hikari>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Adrian Perez de Castro <aperez@igalia.com> writes:
 
@@ -447,7 +437,6 @@ test_emacs '(let ((message-hidden-headers ''()))
            (notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com")
                (notmuch-show-reply)
                (test-output))'
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: Alex Botero-Lowry <alex.boterolowry@gmail.com>, notmuch@notmuchmail.org
@@ -455,7 +444,6 @@ Subject: Re: [notmuch] preliminary FreeBSD support
 In-Reply-To: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
 Fcc: ${MAIL_DIR}/sent
 References: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Alex Botero-Lowry <alex.boterolowry@gmail.com> writes:
 
@@ -521,7 +509,6 @@ test_emacs "(let ((message-hidden-headers '()))
            (notmuch-show \"id:${gen_msg_id}\")
            (notmuch-show-reply)
            (test-output))"
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: 
@@ -529,7 +516,6 @@ Subject: Re: Reply within emacs to an html-only message
 In-Reply-To: <${gen_msg_id}>
 Fcc: ${MAIL_DIR}/sent
 References: <${gen_msg_id}>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Notmuch Test Suite <test_suite@notmuchmail.org> writes:
 
@@ -546,7 +532,6 @@ test_emacs "(let ((message-hidden-headers '()))
              (notmuch-show \"id:$message_id\")
              (notmuch-show-reply)
              (test-output))"
-sed -i -e 's,^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' OUTPUT
 cat <<EOF >EXPECTED
 From: Notmuch Test Suite <test_suite@notmuchmail.org>
 To: 
@@ -554,7 +539,6 @@ Subject: Re: Quote MML tags in reply
 In-Reply-To: <test-emacs-mml-quoting@message.id>
 Fcc: ${MAIL_DIR}/sent
 References: <test-emacs-mml-quoting@message.id>
-User-Agent: Notmuch/XXX Emacs/XXX
 --text follows this line--
 Notmuch Test Suite <test_suite@notmuchmail.org> writes:
 
@@ -760,8 +744,8 @@ bought
 inbox,stashtest
 ${gen_msg_filename}
 http://mid.gmane.org/bought
-http://marc.info/?i=bought
-http://mid.mail-archive.com/bought
+https://marc.info/?i=bought
+https://mid.mail-archive.com/bought
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
index 3f18ec1a34fc631f9e7d15b49fffe83cc3f741b6..b3dbb1b543e9e0a9e8d83821ddf1e182f5fb0a57 100755 (executable)
@@ -5,7 +5,7 @@
 
 # This test tests whether hiding Xapian::Error symbols in libnotmuch
 # also hides them for other users of libxapian. This is motivated by
-# the discussion in http://gcc.gnu.org/wiki/Visibility'
+# the discussion in https://gcc.gnu.org/wiki/Visibility'
 
 test_description='exception symbol hiding'
 
@@ -13,9 +13,8 @@ test_description='exception symbol hiding'
 
 test_begin_subtest 'running test' run_test
 mkdir -p ${PWD}/fakedb/.notmuch
-( LD_LIBRARY_PATH="$TEST_DIRECTORY/../lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
-                $TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent \
-                2>&1 | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,${NOTMUCH_DEFAULT_XAPIAN_BACKEND},backend,g") > OUTPUT
+$TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \
+       | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,${NOTMUCH_DEFAULT_XAPIAN_BACKEND},backend,g" > OUTPUT
 
 cat <<EOF > EXPECTED
 A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
index d5cade8fbecd7d5ddd4a5c71f1f65470861703b7..20e06917fa3f2616750910f4ea58e0fa8b51ad1a 100755 (executable)
@@ -83,4 +83,20 @@ EOF
 notmuch count --output=threads tag:inbox > EXPECTED
 test_expect_equal_file OUTPUT EXPECTED
 
+test_begin_subtest "get all tags"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@t = @db.all_tags()
+for tag in @t do
+   print tag,"\n"
+end
+EOF
+notmuch search --output=tags '*' > EXPECTED
+test_expect_equal_file OUTPUT EXPECTED
+
 test_done
index f5cea421d785d380985eb0d092c0265821d7aab2..198a2e609ac7778642e42f2e1b48ce20634cd41a 100755 (executable)
@@ -12,6 +12,12 @@ test_begin_subtest "Absolute date range with 'same' operator"
 output=$(notmuch search date:2010-12-16..! | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
 
+if [ "${NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR}" = "1" ]; then
+    test_begin_subtest "Absolute date field"
+    output=$(notmuch search date:2010-12-16 | notmuch_search_sanitize)
+    test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
+fi
+
 test_begin_subtest "Absolute time range with TZ"
 notmuch search date:18-Nov-2009_02:19:26-0800..2009-11-18_04:49:52-06:00 | notmuch_search_sanitize > OUTPUT
 cat <<EOF >EXPECTED
diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh
new file mode 100755 (executable)
index 0000000..e8c078d
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env bash
+test_description="library config API"
+
+. ./test-lib.sh || exit 1
+
+add_email_corpus
+
+cat <<EOF > c_head
+#include <string.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   char *val;
+   notmuch_status_t stat;
+
+   EXPECT0(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+
+EOF
+
+cat <<EOF > c_tail
+   EXPECT0(notmuch_database_destroy(db));
+}
+EOF
+
+test_begin_subtest "notmuch_database_{set,get}_config"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   EXPECT0(notmuch_database_set_config (db, "testkey1", "testvalue1"));
+   EXPECT0(notmuch_database_set_config (db, "testkey2", "testvalue2"));
+   EXPECT0(notmuch_database_get_config (db, "testkey1", &val));
+   printf("testkey1 = %s\n", val);
+   EXPECT0(notmuch_database_get_config (db, "testkey2", &val));
+   printf("testkey2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testkey1 = testvalue1
+testkey2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "notmuch_database_get_config_list: empty list"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_get_config_list (db, "nonexistent", &list));
+   printf("valid = %d\n", notmuch_config_list_valid (list));
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+valid = 0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "notmuch_database_get_config_list: all pairs"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_set_config (db, "zzzafter", "afterval"));
+   EXPECT0(notmuch_database_set_config (db, "aaabefore", "beforeval"));
+   EXPECT0(notmuch_database_get_config_list (db, "", &list));
+   for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+      printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+   }
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+aaabefore beforeval
+testkey1 testvalue1
+testkey2 testvalue2
+zzzafter afterval
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_database_get_config_list: one prefix"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_get_config_list (db, "testkey", &list));
+   for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+      printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+   }
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testkey1 testvalue1
+testkey2 testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump config"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+    EXPECT0(notmuch_database_set_config (db, "key with spaces", "value, with, spaces!"));
+}
+EOF
+notmuch dump --include=config >OUTPUT
+cat <<'EOF' >EXPECTED
+#notmuch-dump batch-tag:2 config
+#@ aaabefore beforeval
+#@ key%20with%20spaces value,%20with,%20spaces%21
+#@ testkey1 testvalue1
+#@ testkey2 testvalue2
+#@ zzzafter afterval
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "restore config"
+notmuch dump --include=config >EXPECTED
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+    EXPECT0(notmuch_database_set_config (db, "testkey1", "mutatedvalue"));
+}
+EOF
+notmuch restore --include=config <EXPECTED
+notmuch dump --include=config >OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T600-named-queries.sh b/test/T600-named-queries.sh
new file mode 100755 (executable)
index 0000000..f0ae24f
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+test_description='named queries'
+. ./test-lib.sh || exit 1
+
+QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
+
+test_expect_code 1 "error adding named query before initializing DB" \
+                "notmuch config set query.test \"$QUERYSTR\""
+
+add_email_corpus
+
+test_expect_success "adding named query" \
+                   "notmuch config set query.test \"$QUERYSTR\""
+
+QUERYSTR2="query:test and subject:Maildir"
+test_expect_success "adding nested named query" \
+                   "notmuch config set query.test2 \"$QUERYSTR2\""
+
+test_begin_subtest "retrieve named query"
+output=$(notmuch config get query.test)
+test_expect_equal "$QUERYSTR" "$output"
+
+test_begin_subtest "List all queries"
+notmuch config list | grep ^query | notmuch_config_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+query.test=date:2009-11-18..2009-11-18 and tag:unread
+query.test2=query:test and subject:Maildir
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump named queries"
+notmuch dump | grep '^#@' > OUTPUT
+cat<<EOF > QUERIES.BEFORE
+#@ query.test date%3a2009-11-18..2009-11-18%20and%20tag%3aunread
+#@ query.test2 query%3atest%20and%20subject%3aMaildir
+EOF
+test_expect_equal_file QUERIES.BEFORE OUTPUT
+
+test_begin_subtest "delete named queries"
+notmuch dump > BEFORE
+notmuch config set query.test
+notmuch dump | grep '^#@' > OUTPUT
+cat<<EOF > EXPECTED
+#@ query.test2 query%3atest%20and%20subject%3aMaildir
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "restore named queries"
+notmuch restore < BEFORE
+notmuch dump | grep '^#@' > OUTPUT
+test_expect_equal_file QUERIES.BEFORE OUTPUT
+
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 1 ]; then
+    test_begin_subtest "search named query"
+    notmuch search query:test > OUTPUT
+    notmuch search $QUERYSTR > EXPECTED
+    test_expect_equal_file EXPECTED OUTPUT
+
+    test_begin_subtest "search named query with other terms"
+    notmuch search query:test and subject:Maildir > OUTPUT
+    notmuch search $QUERYSTR and subject:Maildir > EXPECTED
+    test_expect_equal_file EXPECTED OUTPUT
+
+    test_begin_subtest "search nested named query"
+    notmuch search query:test2 > OUTPUT
+    notmuch search $QUERYSTR2 > EXPECTED
+    test_expect_equal_file EXPECTED OUTPUT
+fi
+
+test_done
diff --git a/test/T620-lock.sh b/test/T620-lock.sh
new file mode 100755 (executable)
index 0000000..f46475e
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+test_description="locking"
+. ./test-lib.sh || exit 1
+
+if [ "${NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK}" = "0" ]; then
+    test_subtest_missing_external_prereq_["lock retry support"]=t
+fi
+
+add_email_corpus
+
+test_begin_subtest "blocking open"
+test_C ${MAIL_DIR} <<'EOF'
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <notmuch-test.h>
+
+void
+taggit (notmuch_database_t *db, const char *tag)
+{
+    notmuch_message_t *message;
+
+    EXPECT0 (notmuch_database_find_message (db, "4EFC743A.3060609@april.org", &message));
+    if (message == NULL) {
+       fprintf (stderr, "unable to find message");
+       exit (1);
+    }
+
+    EXPECT0 (notmuch_message_add_tag (message, tag));
+    notmuch_message_destroy (message);
+}
+
+int
+main (int argc, char **argv)
+{
+    pid_t child;
+    const char *path = argv[1];
+
+    child = fork ();
+    if (child == -1) {
+       fprintf (stderr, "fork failed\n");
+       exit (1);
+    }
+
+    if (child == 0) {
+       notmuch_database_t *db2;
+
+       sleep (1);
+       EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &db2));
+       taggit (db2, "child");
+       EXPECT0 (notmuch_database_close (db2));
+    } else {
+       notmuch_database_t *db;
+
+       EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+       taggit (db, "parent");
+       sleep (2);
+       EXPECT0 (notmuch_database_close (db));
+       wait (NULL);
+    }
+}
+
+EOF
+notmuch search --output=tags id:4EFC743A.3060609@april.org >> OUTPUT
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+child
+inbox
+parent
+unread
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
index b8c3a67cee54e8b7ad7e2c69294193e2c501b801..42f665597b48b5fd7d9dec235b67888e87936647 100644 (file)
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: David Bremner <david@tethera.net>
  */
index 4721b8b0875d5267d7907d73f80f8165dc538fb6..3bbb114ab85e7d49896e018c7432c5086db8a2fb 100644 (file)
@@ -109,12 +109,14 @@ http://notmuchmail.org/mailman/listinfo/notmuch
            To: notmuch@notmuchmail.org
            Date: Wed, 18 Nov 2009 02:50:48 +0600
 
-           Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu
-           did gyre and gimble:
+           Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at
+           seas.harvard.edu did gyre and gimble:
 
             LK> Is the list archived anywhere?  The obvious archives
-            LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
-            LK> think I subscribed too late to get the patch (I only just saw the
+            LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available,
+           and I
+            LK> think I subscribed too late to get the patch (I only just saw
+           the
             LK> discussion about it).
 
             LK> It doesn't look like the patch is in git yet.
@@ -141,7 +143,8 @@ http://notmuchmail.org/mailman/listinfo/notmuch
            seas.harvard.edu> wrote:
            > > See the patch just posted here.
 
-           I've also pushed a slightly more complicated (and complete) fix to my
+           I've also pushed a slightly more complicated (and complete) fix to
+           my
            private notmuch repository
 
            git://keithp.com/git/notmuch
@@ -164,10 +167,12 @@ http://notmuchmail.org/mailman/listinfo/notmuch
                [ multipart/signed ]
                [ Unknown signature status ]
                [ text/plain ]
-               > I've also pushed a slightly more complicated (and complete) fix to my
+               > I've also pushed a slightly more complicated (and complete)
+               > fix to my
                > private notmuch repository
 
-               The version of lib/messages.cc in your repo doesn't build because it's
+               The version of lib/messages.cc in your repo doesn't build
+               because it's
                missing "#include <stdint.h>" (for the uint32_t on line 466).
 
                [ 4-line signature. Click/Enter to show. ]
index 62a463535e5bbe3231994959c8913be7731f668f..620caa0064f6440d75c26c705378e77089e5d609 100644 (file)
@@ -49,8 +49,8 @@ Date: Wed, 18 Nov 2009 01:02:38 +0600
 [ Unknown signature status ]
 [ text/plain ]
 
-Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did
-gyre and gimble:
+Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did gyre
+and gimble:
 
  LK> Resulted in 4604 lines of errors along the lines of:
 
@@ -109,8 +109,8 @@ Subject: [notmuch] Working with Maildir storage?
 To: notmuch@notmuchmail.org
 Date: Wed, 18 Nov 2009 02:50:48 +0600
 
-Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu
-did gyre and gimble:
+Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did
+gyre and gimble:
 
  LK> Is the list archived anywhere?  The obvious archives
  LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
index b8437127c01fccc98c6f9be595f6cd1b8b0ebb5c..e7d3151ce8cbb528c68d6526ba4c39a035d5a1eb 100755 (executable)
@@ -3,6 +3,7 @@
 # Run tests
 #
 # Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
 #
 # Adapted from a Makefile to a shell script by Carl Worth (2010)
 
@@ -14,16 +15,16 @@ if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
     exit 1
 fi
 
-cd $(dirname "$0")
+cd "$(dirname "$0")"
 
-TESTS=${NOTMUCH_TESTS:-`echo T[0-9][0-9][0-9]-*.sh`}
+TESTS=${NOTMUCH_TESTS:-T[0-9][0-9][0-9]-*.sh}
 
 # Clean up any results from a previous run
-rm -r test-results >/dev/null 2>/dev/null
+rm -rf test-results
 
-# test for timeout utility
+# Test for timeout utility
 if command -v timeout >/dev/null; then
-    TEST_TIMEOUT_CMD="timeout 2m "
+    TEST_TIMEOUT_CMD="timeout 2m"
     echo "INFO: using 2 minute timeout for tests"
 else
     TEST_TIMEOUT_CMD=""
diff --git a/test/notmuch-test.h b/test/notmuch-test.h
new file mode 100644 (file)
index 0000000..d39febb
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef _NOTMUCH_TEST_H
+#define _NOTMUCH_TEST_H
+#include <stdio.h>
+#include <notmuch.h>
+
+inline static void
+expect0(int line, notmuch_status_t ret)
+{
+   if (ret) {
+       fprintf (stderr, "line %d: %s\n", line, ret);
+       exit (1);
+   }
+}
+
+#define EXPECT0(v)  expect0(__LINE__, v);
+#endif
index 901a4dde8ecbfe65444950a283637f0d830c8e70..694761cf1742d1179d6e5e80df61ff2f856f306b 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
index d74271d932312d77ea66a02131a753ea4b931762..aca694a3c1d364c12d3a0ca2c05984ddda261433 100644 (file)
@@ -19,7 +19,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: David Bremner <david@tethera.net>
  */
index bb136687b2fd31a29775d3a25550c1f795ec034a..a629927c263c461582a872a961ae59f6329f3e29 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Authors: Carl Worth <cworth@cworth.org>
  */
index ff333a1d86f75e62a394e7fb1dac3c6c18b457f1..dcc8863c70d8ee973687eb30580e02640e214f3e 100644 (file)
@@ -1,6 +1,6 @@
 # -*- makefile -*-
 
-TEST_DATABASE_MIRROR=http://notmuchmail.org/releases/test-databases
+TEST_DATABASE_MIRROR=https://notmuchmail.org/releases/test-databases
 
 dir := test/test-databases
 
index 4e17b781e26298c3abefec9ec7d2c1e7e9e28644..03ef1d2d02467674f4f096fcd872a409fe7421fe 100644 (file)
@@ -1,5 +1,6 @@
 #
 # Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see http://www.gnu.org/licenses/ .
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
 
 # This file contains common code to be used by both the regular
 # (correctness) tests and the performance tests.
 
+# test-lib.sh defines die() which echoes to nonstandard fd where
+# output was redirected earlier in that file. If test-lib.sh is not
+# loaded, neither this redirection nor die() function were defined.
+#
+type die >/dev/null 2>&1 || die () { echo "$@" >&2; exit 1; }
+
 find_notmuch_path ()
 {
     dir="$1"
@@ -51,6 +58,11 @@ restore_database () {
 TEST_DIRECTORY=$(pwd -P)
 notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"`
 
+# Prepend $TEST_DIRECTORY/../lib to LD_LIBRARY_PATH, to make tests work
+# on systems where ../notmuch depends on LD_LIBRARY_PATH.
+LD_LIBRARY_PATH=${TEST_DIRECTORY%/*}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
+export LD_LIBRARY_PATH
+
 # configure output
 . $notmuch_path/sh.config || exit 1
 
index 596a705103ef1a3bd1c7902fa7d58f97f9902344..9946010bfb9e37f1ff332493c3a55e3024233187 100644 (file)
@@ -16,7 +16,7 @@
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; Authors: Dmitry Kurochkin <dmitry.kurochkin@gmail.com>
 
@@ -184,6 +184,11 @@ nothing."
 (setq notmuch-tag-deleted-formats
       '((".*" nil)))
 
+;; Also for historical reasons, we set the fcc handler to file not
+;; insert.
+
+(setq notmuch-maildir-use-notmuch-insert nil)
+
 ;; force a common html renderer, to avoid test variations between
 ;; environments
 
index ac04b15a96ba1da2c1c36759a5b78c030602cd0e..aac0343ba7bfd221744d73ffbf44eae5f3200a83 100644 (file)
@@ -1,5 +1,6 @@
 #
 # Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -12,7 +13,7 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program.  If not, see http://www.gnu.org/licenses/ .
+# along with this program.  If not, see https://www.gnu.org/licenses/ .
 
 if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
     echo "Error: The notmuch test suite requires a bash version >= 4.0"
@@ -222,15 +223,15 @@ test_fixed=0
 test_broken=0
 test_success=0
 
-_die_common () {
+_exit_common () {
        code=$?
        trap - EXIT
        set +ex
        rm -rf "$TEST_TMPDIR"
 }
 
-die () {
-       _die_common
+trap_exit () {
+       _exit_common
        if test -n "$GIT_EXIT_OK"
        then
                exit $code
@@ -244,17 +245,27 @@ die () {
        fi
 }
 
-die_signal () {
-       _die_common
+trap_signal () {
+       _exit_common
        echo >&6 "FATAL: $0: interrupted by signal" $((code - 128))
        exit $code
 }
 
+die () {
+       _exit_common
+       exec >&6
+       say_color error '%-6s' FATAL
+       echo " $*"
+       echo
+       echo "Unexpected exit while executing $0."
+       exit 1
+}
+
 GIT_EXIT_OK=
 # Note: TEST_TMPDIR *NOT* exported!
 TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
-trap 'die' EXIT
-trap 'die_signal' HUP INT TERM
+trap 'trap_exit' EXIT
+trap 'trap_signal' HUP INT TERM
 
 test_decode_color () {
        sed     -e 's/.\[1m/<WHITE>/g' \
@@ -543,7 +554,7 @@ add_email_corpus ()
        cp -a $TEST_DIRECTORY/corpus.mail ${MAIL_DIR}
     else
        cp -a $TEST_DIRECTORY/corpus ${MAIL_DIR}
-       notmuch new >/dev/null
+       notmuch new >/dev/null || die "'notmuch new' failed while adding email corpus"
        cp -a ${MAIL_DIR} $TEST_DIRECTORY/corpus.mail
     fi
 }
@@ -673,6 +684,12 @@ NOTMUCH_NEW ()
     notmuch new "${@}" | grep -v -E -e '^Processed [0-9]*( total)? file|Found [0-9]* total file'
 }
 
+NOTMUCH_DUMP_TAGS ()
+{
+    # this relies on the default format being batch-tag, otherwise some tests will break
+    notmuch dump --include=tags "${@}" | sed '/^#/d' | sort
+}
+
 notmuch_search_sanitize ()
 {
     perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/'
@@ -733,6 +750,17 @@ notmuch_uuid_sanitize ()
 {
     sed 's/[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}/UUID/g'
 }
+
+notmuch_built_with_sanitize ()
+{
+    sed 's/^built_with[.]\(.*\)=.*$/built_with.\1=something/'
+}
+
+notmuch_config_sanitize ()
+{
+    notmuch_dir_sanitize | notmuch_built_with_sanitize
+}
+
 # End of notmuch helper functions
 
 # Use test_set_prereq to tell that a particular prerequisite is available.
@@ -1164,15 +1192,13 @@ test_emacs () {
 }
 
 test_python() {
-       export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib
-       export PYTHONPATH=$TEST_DIRECTORY/../bindings/python
-
-       (echo "import sys; _orig_stdout=sys.stdout; sys.stdout=open('OUTPUT', 'w')"; cat) \
-               | $NOTMUCH_PYTHON -
+    # Note: if there is need to print debug information from python program,
+    # use stdout = os.fdopen(6, 'w') or stderr = os.fdopen(7, 'w')
+    PYTHONPATH="$TEST_DIRECTORY/../bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
+       $NOTMUCH_PYTHON -B - > OUTPUT
 }
 
 test_ruby() {
-    export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib
     MAIL_DIR=$MAIL_DIR ruby -I $TEST_DIRECTORY/../bindings/ruby> OUTPUT
 }
 
@@ -1180,8 +1206,7 @@ test_C () {
     exec_file="test${test_count}"
     test_file="${exec_file}.c"
     cat > ${test_file}
-    export LD_LIBRARY_PATH=${TEST_DIRECTORY}/../lib
-    ${TEST_CC} ${TEST_CFLAGS} -I${TEST_DIRECTORY}/../lib -o ${exec_file} ${test_file} -L${TEST_DIRECTORY}/../lib/ -lnotmuch -ltalloc
+    ${TEST_CC} ${TEST_CFLAGS} -I${TEST_DIRECTORY} -I${TEST_DIRECTORY}/../lib -o ${exec_file} ${test_file} -L${TEST_DIRECTORY}/../lib/ -lnotmuch -ltalloc
     echo "== stdout ==" > OUTPUT.stdout
     echo "== stderr ==" > OUTPUT.stderr
     ./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr
index d6e60fc99e4e3dfb0de12fc5ebca09177f8fdea6..778bbd522227b73d8aaaa53df64a17e1061e2ba6 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 17c8727d13b2a743dd3afd9642026f3a73b2b1ea..4bb338a2abeacd1253c1287c39f8d6769e1c5405 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index b4a2a02aebd825e7d19f19ffb2120690818cb665..8883ff903baff0d236e4a490a9957683d8b500c2 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: David Bremner <david@tethera.net>
  */
index 92af937f45ec15eab8ae00d27b287bf6a265134a..18125309ebe9ed33c048f928e02d1e6b35edca28 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Jani Nikula <jani@nikula.org>
  */
index ac496daf17f7c6f5f1dab6aabe8ff7e6dd8f4793..f211eaaaac5c4f6a8027fea66be985a6cf8edb7c 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index b84e0e257e806937d37d41e1a00b44d5d6c81f73..4829f33c9ec8c73875d3f378382ad6c55625f6f8 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: Carl Worth <cworth@cworth.org>
  */
index 2e704457ebde863562419d2c7220b1aa69e3877b..2b2cd8f9bda2af8a7dd2590c0ee3dfb10b066c5d 100644 (file)
@@ -13,7 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ * along with this program.  If not, see https://www.gnu.org/licenses/ .
  *
  * Author: David Bremner <david@tethera.net>
  */