]> git.notmuchmail.org Git - notmuch/commitdiff
python/doc: fix link to notmuch-config manpage master
authorDavid Bremner <david@tethera.net>
Sun, 22 Jun 2025 19:32:21 +0000 (16:32 -0300)
committerDavid Bremner <david@tethera.net>
Fri, 4 Jul 2025 10:35:39 +0000 (07:35 -0300)
This docstring has been like this a long time, but recently I noticed
sphinx-doc complaining about :man:. This :any: link seems to work, and
is consistent with the other links to the notmuch-config man page.

238 files changed:
.gitignore
Makefile.global
Makefile.local
NEWS
bindings/Makefile.local
bindings/python-cffi/notmuch2/__init__.py
bindings/python-cffi/notmuch2/_base.py
bindings/python-cffi/notmuch2/_config.py
bindings/python-cffi/notmuch2/_database.py
bindings/python-cffi/notmuch2/_message.py
bindings/python-cffi/notmuch2/_query.py
bindings/python-cffi/notmuch2/_tags.py
bindings/python-cffi/setup.py
bindings/python-cffi/tests/test_database.py
bindings/python-cffi/tests/test_message.py
bindings/python/.gitignore [deleted file]
bindings/python/MANIFEST.in [deleted file]
bindings/python/README [deleted file]
bindings/python/docs/COPYING [deleted file]
bindings/python/docs/Makefile [deleted file]
bindings/python/docs/source/conf.py [deleted file]
bindings/python/docs/source/database.rst [deleted file]
bindings/python/docs/source/filesystem.rst [deleted file]
bindings/python/docs/source/index.rst [deleted file]
bindings/python/docs/source/message.rst [deleted file]
bindings/python/docs/source/messages.rst [deleted file]
bindings/python/docs/source/notes.rst [deleted file]
bindings/python/docs/source/query.rst [deleted file]
bindings/python/docs/source/quickstart.rst [deleted file]
bindings/python/docs/source/status_and_errors.rst [deleted file]
bindings/python/docs/source/tags.rst [deleted file]
bindings/python/docs/source/thread.rst [deleted file]
bindings/python/docs/source/threads.rst [deleted file]
bindings/python/notmuch/__init__.py [deleted file]
bindings/python/notmuch/compat.py [deleted file]
bindings/python/notmuch/database.py [deleted file]
bindings/python/notmuch/directory.py [deleted file]
bindings/python/notmuch/errors.py [deleted file]
bindings/python/notmuch/filenames.py [deleted file]
bindings/python/notmuch/globals.py [deleted file]
bindings/python/notmuch/message.py [deleted file]
bindings/python/notmuch/messages.py [deleted file]
bindings/python/notmuch/query.py [deleted file]
bindings/python/notmuch/tag.py [deleted file]
bindings/python/notmuch/thread.py [deleted file]
bindings/python/notmuch/threads.py [deleted file]
bindings/python/notmuch/version.py [deleted file]
bindings/python/setup.py [deleted file]
bindings/ruby/database.c
bindings/ruby/defs.h
bindings/ruby/directory.c
bindings/ruby/extconf.rb
bindings/ruby/filenames.c
bindings/ruby/init.c
bindings/ruby/message.c
bindings/ruby/messages.c
bindings/ruby/query.c
bindings/ruby/tags.c
bindings/ruby/thread.c
compat/have_strcasestr.c
completion/notmuch-completion.bash
completion/zsh/_notmuch
configure
contrib/notmuch-mutt/README
contrib/notmuch-mutt/notmuch-mutt
contrib/python-legacy/.gitignore [new file with mode: 0644]
contrib/python-legacy/MANIFEST.in [new file with mode: 0644]
contrib/python-legacy/README [new file with mode: 0644]
contrib/python-legacy/docs/COPYING [new file with mode: 0644]
contrib/python-legacy/docs/Makefile [new file with mode: 0644]
contrib/python-legacy/docs/source/conf.py [new file with mode: 0644]
contrib/python-legacy/docs/source/database.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/filesystem.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/index.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/message.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/messages.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/notes.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/query.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/quickstart.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/status_and_errors.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/tags.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/thread.rst [new file with mode: 0644]
contrib/python-legacy/docs/source/threads.rst [new file with mode: 0644]
contrib/python-legacy/notmuch/__init__.py [new file with mode: 0644]
contrib/python-legacy/notmuch/compat.py [new file with mode: 0644]
contrib/python-legacy/notmuch/database.py [new file with mode: 0644]
contrib/python-legacy/notmuch/directory.py [new file with mode: 0644]
contrib/python-legacy/notmuch/errors.py [new file with mode: 0644]
contrib/python-legacy/notmuch/filenames.py [new file with mode: 0644]
contrib/python-legacy/notmuch/globals.py [new file with mode: 0644]
contrib/python-legacy/notmuch/message.py [new file with mode: 0644]
contrib/python-legacy/notmuch/messages.py [new file with mode: 0644]
contrib/python-legacy/notmuch/query.py [new file with mode: 0644]
contrib/python-legacy/notmuch/tag.py [new file with mode: 0644]
contrib/python-legacy/notmuch/thread.py [new file with mode: 0644]
contrib/python-legacy/notmuch/threads.py [new file with mode: 0644]
contrib/python-legacy/notmuch/version.py [new file with mode: 0644]
contrib/python-legacy/setup.py [new file with mode: 0644]
debian/changelog
debian/control
debian/libnotmuch5.install [deleted file]
debian/libnotmuch5.symbols [deleted file]
debian/libnotmuch5t64.install [new file with mode: 0644]
debian/libnotmuch5t64.lintian-overrides [new file with mode: 0644]
debian/libnotmuch5t64.symbols [new file with mode: 0644]
debian/notmuch-vim.dirs [deleted file]
debian/notmuch-vim.install
debian/notmuch-vim.neovim-addon [new symlink]
debian/notmuch-vim.vim-addon [new file with mode: 0644]
debian/rules
debian/tests/control [new file with mode: 0644]
devel/emacs-keybindings.org
devel/notmuch-web/nmweb.py
devel/notmuch-web/templates/show.html
devel/release-checks.sh
devel/schemata
doc/Makefile.local
doc/conf.py
doc/man1/notmuch-config.rst
doc/man1/notmuch-git.rst
doc/man1/notmuch-search.rst
doc/man1/notmuch-show.rst
doc/man7/notmuch-sexp-queries.rst
doc/notmuch-emacs.rst
emacs/notmuch-address.el
emacs/notmuch-emacs-mua
emacs/notmuch-hello.el
emacs/notmuch-jump.el
emacs/notmuch-lib.el
emacs/notmuch-maildir-fcc.el
emacs/notmuch-mua.el
emacs/notmuch-parser.el
emacs/notmuch-print.el
emacs/notmuch-show.el
emacs/notmuch-tag.el
emacs/notmuch-tree.el
emacs/notmuch-wash.el
emacs/notmuch.el
lib/config.cc
lib/database-private.h
lib/database.cc
lib/index.cc
lib/message-property.cc
lib/message.cc
lib/notmuch-private.h
lib/notmuch.h
lib/open.cc
lib/parse-sexp.cc
lib/query.cc
lib/regexp-fields.cc
lib/string-map.c
lib/thread.cc
notmuch-client.h
notmuch-config.c
notmuch-git.py
notmuch-new.c
notmuch-show.c
notmuch.c
performance-test/README
performance-test/T02-tag.sh
performance-test/download/notmuch-email-corpus-0.5.tar.xz.asc [new file with mode: 0644]
performance-test/version.sh
test/README
test/T000-basic.sh
test/T010-help-test.sh
test/T030-config.sh
test/T040-setup.sh
test/T050-new.sh
test/T055-path-config.sh
test/T060-count.sh
test/T080-search.sh
test/T081-sexpr-search.sh
test/T095-address.sh
test/T131-show-limiting.sh [new file with mode: 0755]
test/T150-tagging.sh
test/T160-json.sh
test/T206-author-separator.sh [new file with mode: 0755]
test/T220-reply.sh
test/T240-dump-restore.sh
test/T310-emacs.sh
test/T312-emacs-hl-line.sh [new file with mode: 0755]
test/T350-crypto.sh
test/T355-smime.sh
test/T360-symbol-hiding.sh
test/T390-python.sh [deleted file]
test/T391-python-cffi.sh
test/T392-python-cffi-notmuch.sh
test/T395-ruby.sh
test/T410-argument-parsing.sh
test/T450-emacs-show.sh
test/T460-emacs-tree.sh
test/T461-emacs-search-exclude.sh [new file with mode: 0755]
test/T480-hex-escaping.sh
test/T490-parse-time-string.sh
test/T520-show.sh
test/T550-db-features.sh
test/T566-lib-message.sh
test/T570-revision-tracking.sh
test/T590-libconfig.sh
test/T592-thread-breakage.sh
test/T610-message-property.sh
test/T630-emacs-draft.sh
test/T700-reindex.sh
test/T710-message-id.sh
test/T760-as-text.sh [new file with mode: 0755]
test/T770-emacs-subject-warnings.sh [new file with mode: 0755]
test/T800-asan.sh
test/T810-tsan.sh [new file with mode: 0755]
test/T810-tsan.suppressions [new file with mode: 0644]
test/T850-git.sh
test/corpora/attachment/text-calendar.eml [new file with mode: 0644]
test/corpora/indexing/fake-pdf:2,S [new file with mode: 0644]
test/emacs-exclude.expected-output/notmuch-search-tag-inbox-with-excluded [new file with mode: 0644]
test/emacs-exclude.expected-output/notmuch-search-tag-inbox-without-excluded [new file with mode: 0644]
test/emacs-exclude.expected-output/notmuch-tree-tag-inbox-with-excluded [new file with mode: 0644]
test/emacs-exclude.expected-output/notmuch-tree-tag-inbox-without-excluded [new file with mode: 0644]
test/emacs-exclude.expected-output/notmuch-unthreaded-tag-inbox-with-excluded [new file with mode: 0644]
test/emacs-exclude.expected-output/notmuch-unthreaded-tag-inbox-without-excluded [new file with mode: 0644]
test/emacs-subject-warnings.el [new file with mode: 0644]
test/emacs-tree.expected-output/inbox-outline [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-single-thread
test/emacs-tree.expected-output/notmuch-tree-tag-inbox
test/emacs-tree.expected-output/notmuch-tree-tag-inbox-oldest-first [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-tag-inbox-tagged
test/emacs-tree.expected-output/notmuch-tree-tag-inbox-thread-tagged
test/emacs-tree.expected-output/result-format-function
test/emacs-unthreaded.expected-output/result-format-function
test/export-dirs.sh
test/notmuch-test
test/setup.expected-output/config-with-comments
test/test-lib-common.sh
test/test-lib-emacs.sh
test/test-lib.el
test/test-lib.sh
util/xapian-extra.h [new file with mode: 0644]
version.txt
vim/README
vim/notmuch.txt

index f94d1480c1f91d5ff64e8a0f746f4f79de19b839..eda6d9cff7473613aca1e36d54fb4dd5e8872a2c 100644 (file)
@@ -21,3 +21,4 @@
 /bindings/python-cffi/_notmuch_config.py
 TAGS
 tags
 /bindings/python-cffi/_notmuch_config.py
 TAGS
 tags
+__pycache__
index 7a7a3c6d09ab687e6899cadd8055f83e6dbba07a..258c379c73daec8dba7bff1210a6cf8fc320ffd6 100644 (file)
@@ -47,8 +47,6 @@ DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.xz
 SHA256_FILE=$(TAR_FILE).sha256.asc
 DETACHED_SIG_FILE=$(TAR_FILE).asc
 
 SHA256_FILE=$(TAR_FILE).sha256.asc
 DETACHED_SIG_FILE=$(TAR_FILE).asc
 
-PV_FILE=bindings/python/notmuch/version.py
-
 # Smash together user's values with our extra values
 FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(WARN_CFLAGS) $(extra_cflags) $(CPPFLAGS) $(CONFIGURE_CFLAGS) $(CFLAGS)
 FINAL_CXXFLAGS = $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CPPFLAGS) $(CONFIGURE_CXXFLAGS) $(CXXFLAGS)
 # Smash together user's values with our extra values
 FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(WARN_CFLAGS) $(extra_cflags) $(CPPFLAGS) $(CONFIGURE_CFLAGS) $(CFLAGS)
 FINAL_CXXFLAGS = $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CPPFLAGS) $(CONFIGURE_CXXFLAGS) $(CXXFLAGS)
index 7699c208bb8eca696a35ae7ea0951a6fd8c44487..2cc9bd29a2f40c14b3e1a8973d6969b20a6ddfac 100644 (file)
@@ -60,9 +60,7 @@ dist: $(TAR_FILE)
 .PHONY: update-versions
 
 update-versions:
 .PHONY: update-versions
 
 update-versions:
-       sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \
-           -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \
-           ${PV_FILE}
+       @echo nothing to do
 
 # We invoke make recursively only to force ordering of our phony
 # targets in the case of parallel invocation of make (-j).
 
 # We invoke make recursively only to force ordering of our phony
 # targets in the case of parallel invocation of make (-j).
diff --git a/NEWS b/NEWS
index b83551f1f132c812e9fc888ca7fb90d57eee666a..0d284b11fab331c0bbc4e6db3e9e5aad0b042ee0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,181 @@
+Notmuch 0.39 (2025-03-15)
+=========================
+
+General
+-------
+
+Allow to customize the separator between individual and matched and
+non-matched authors when showing threads. See
+`search.authors_separator` and `search.authors_matched_separator` in
+notmuch-config(1).
+
+Library
+-------
+
+Make the s-expression query parser thread-safe.
+
+Emacs
+-----
+
+The new variable `untrusted-content` is now (conditionally) set in
+relevant modes.
+
+Add `notmuch-mua-subject-regexp` to help avoid sending erroneous
+subjects.
+
+Allow toggling display of excluded messages.
+
+Fix for text/calendar display. Fix for subject sanitizing in tree
+mode. Fix for hl-line location in search and tree mode. Fix for
+interaction with which-func-mode and imenu. Fix for saving hidden
+headers in drafts.
+
+Support for Emacs older than 27.1 is deprecated with this release.
+
+Legacy Python Bindings
+----------------------
+
+The legacy python bindings previously in `bindings/python` are no
+longer supported. These bindings have been deprecated since 2020. As
+of this release they are no longer built or tested, and patches for
+them will no longer be accepted. They have been moved to `contrib` to
+smooth the transition. Thanks very much to Sebastian Spaeth and all of
+the other contributors to these bindings over the years.
+
+Python Bindings
+---------------
+
+Adapt python-cffi version handling to stricter version constraints in
+setuptools.
+
+Fix iterator related segfaults.
+
+Ruby Bindings
+-------------
+
+Fix for warnings about "undefining the allocator of T_DATA".
+
+Test Suite
+----------
+
+Adapt test suite for changes in Emacs 30.1 and GnuPG 2.4 series.
+
+Notmuch 0.38.3 (2024-03-09)
+===========================
+
+CLI
+---
+
+Fix a bug in configuration code that caused the notmuch command to
+erroneously report "Error: could not locate database" under some
+circumstances.
+
+Notmuch 0.38.2 (2023-12-01)
+===========================
+
+Library
+-------
+
+Make sorting of string maps lexicographic on (key,value) pairs. This
+avoids some test failures due to variation in message property output
+order.
+
+Emacs
+-----
+
+Avoid extra separators after the last address in `notmuch-emacs-mua`.
+
+
+Notmuch 0.38.1 (2023-10-26)
+===========================
+
+CLI
+---
+
+Report parse errors in config files.
+
+Emacs
+-----
+
+Fix image toggling for Emacs >= 29.1.
+
+notmuch-mutt
+------------
+
+Fix syntax error in script.
+
+Notmuch 0.38 (2023-09-12)
+=========================
+
+General
+-------
+
+Support relative lastmod queries (see notmuch-sexp-queries(7) and
+notmuch-search-terms(7) for details).
+
+Support indexing of designated attachments as text (see
+notmuch-config(1) for details).
+
+CLI
+---
+
+Add options --offset and --limit to notmuch-show(1).
+
+Emacs
+-----
+
+New commands notmuch-search-edit-search and notmuch-tree-edit-search.
+
+Introduce notmuch-tree-outline-mode.
+
+Some compatibility fixes for Emacs 29. At least one issue (hiding
+images) remains in 0.38.
+
+Support completion when piping to external command.
+
+Fix regression in updating tag display introduced by 0.37.
+
+Library
+-------
+
+Fix bug creating database when database.path is not set.
+
+Incremental performance improvements for message deletion.
+
+Catch Xapian exceptions when deleting messages.
+
+Sync removed message properties to the database.
+
+Replace use of thread-unsafe Query::MatchAll in the infix query
+parser.
+
+Notmuch-Mutt
+------------
+
+Be more careful when clearing the results directory.
+
+Ruby
+----
+
+Use `database_open_with_config`, and provide compatible path search
+semantics.
+
+Bugfix for query.get_sort
+
+Test Suite
+----------
+
+Support testing installed version of notmuch.
+
+Adapt to some breaking changes in glib handling of init files.
+
+Replace OpenPGP key used in test suite.
+
+Performance Tests
+-----------------
+
+Update signatures for performance test corpus.
+
 Notmuch 0.37 (2022-08-21)
 =========================
 
 Notmuch 0.37 (2022-08-21)
 =========================
 
@@ -54,7 +232,7 @@ Library
 
 Add the `sexp` prefix to the infix (traditional) query parser. This
 allows specific subqueries to be parsed by the sexp parser (with
 
 Add the `sexp` prefix to the infix (traditional) query parser. This
 allows specific subqueries to be parsed by the sexp parser (with
-appropropriate quoting). See `notmuch-search-terms(7)` for details.
+appropriate quoting). See `notmuch-search-terms(7)` for details.
 
 Add another heuristic to regexp fields to prevent phrase parsing of
 bracketed sub-expressions.
 
 Add another heuristic to regexp fields to prevent phrase parsing of
 bracketed sub-expressions.
@@ -76,7 +254,7 @@ the database.
 Documentation
 -------------
 
 Documentation
 -------------
 
-Reorganize documention for `notmuch-config`. Add a few links from
+Reorganize documentation for `notmuch-config`. Add a few links from
 other man pages.
 
 Emacs
 other man pages.
 
 Emacs
@@ -123,7 +301,7 @@ Fix some small memory leaks in `notmuch_database_open_with_config`.
 CLI
 ---
 
 CLI
 ---
 
-Improve handling of leading/trailing punctation and space for
+Improve handling of leading/trailing punctuation and space for
 configuration lists.
 
 Only ignore `.notmuch` at the top level in `notmuch new`.
 configuration lists.
 
 Only ignore `.notmuch` at the top level in `notmuch new`.
@@ -142,7 +320,7 @@ lazily.
 Don't add space to completion candidates, improves compatibility with
 third party completion frameworks.
 
 Don't add space to completion candidates, improves compatibility with
 third party completion frameworks.
 
-Make citation formating more robust against whitespace.
+Make citation formatting more robust against whitespace.
 
 Use `--excludes=false` when generating the 'All tags' section.
 
 
 Use `--excludes=false` when generating the 'All tags' section.
 
@@ -543,7 +721,7 @@ Notmuch 0.31.2 (2020-11-08)
 Build
 -----
 
 Build
 -----
 
-Catch one more occurence of "version" in the build system, which
+Catch one more occurrence of "version" in the build system, which
 caused the file to be regenerated in the release tarball.
 
 Notmuch 0.31.1 (2020-11-08)
 caused the file to be regenerated in the release tarball.
 
 Notmuch 0.31.1 (2020-11-08)
index 7b10af088abbb98716ae45c4cba35cf7a50ffeb2..72fe9850a23d1ab3bac54e8b47de1fdd7970f49d 100644 (file)
@@ -8,9 +8,9 @@ ruby-bindings: $(dir)/ruby.stamp
 $(dir)/ruby.stamp: lib/$(LINKER_NAME)
 ifeq ($(HAVE_RUBY_DEV),1)
        cd $(dir)/ruby && \
 $(dir)/ruby.stamp: lib/$(LINKER_NAME)
 ifeq ($(HAVE_RUBY_DEV),1)
        cd $(dir)/ruby && \
-               EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \
+               EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS) $(TALLOC_LDFLAGS)" \
+               EXTRA_INCFLAGS="-I$(NOTMUCH_SRCDIR)/lib $(TALLOC_CFLAGS)" \
                LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
                LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
-               NOTMUCH_SRCDIR='$(NOTMUCH_SRCDIR)' \
                $(RUBY) extconf.rb --vendor
        $(MAKE) -C $(dir)/ruby CFLAGS="$(CFLAGS) -pipe -fno-plt -fPIC" && touch $@
 endif
                $(RUBY) extconf.rb --vendor
        $(MAKE) -C $(dir)/ruby CFLAGS="$(CFLAGS) -pipe -fno-plt -fPIC" && touch $@
 endif
@@ -34,3 +34,7 @@ CLEAN += $(patsubst %,$(dir)/ruby/%, \
 CLEAN += bindings/ruby/.vendorarchdir.time $(dir)/ruby.stamp
 
 CLEAN += bindings/python-cffi/build $(dir)/python-cffi.stamp
 CLEAN += bindings/ruby/.vendorarchdir.time $(dir)/ruby.stamp
 
 CLEAN += bindings/python-cffi/build $(dir)/python-cffi.stamp
+CLEAN += bindings/python-cffi/__pycache__
+
+DISTCLEAN += bindings/python-cffi/_notmuch_config.py \
+       bindings/python-cffi/notmuch2.egg-info
index f281edc1a5221e2f5671361c1b670ecaf0de6c06..978e01c2a5e327eec4ce79a2e30fc9042767087c 100644 (file)
@@ -22,7 +22,7 @@ Libnotmuch uses a hierarchical memory allocator, this means all
 objects have a strict parent-child relationship and when the parent is
 freed all the children are freed as well.  This has some implications
 for these Python bindings as parent objects need to be kept alive.
 objects have a strict parent-child relationship and when the parent is
 freed all the children are freed as well.  This has some implications
 for these Python bindings as parent objects need to be kept alive.
-This is normally schielded entirely from the user however and the
+This is normally shielded entirely from the user however and the
 Python objects automatically make sure the right references are kept
 alive.  It is however the reason the :class:`BaseObject` exists as it
 defines the API all Python objects need to implement to work
 Python objects automatically make sure the right references are kept
 alive.  It is however the reason the :class:`BaseObject` exists as it
 defines the API all Python objects need to implement to work
@@ -37,9 +37,9 @@ In these python bindings they have sometimes been exposed as
 like :class:`collections.abc.Set` or :class:`collections.abc.Mapping`
 etc.  This gives a more natural API to work with, e.g. being able to
 treat tags as sets.  However it does mean that the
 like :class:`collections.abc.Set` or :class:`collections.abc.Mapping`
 etc.  This gives a more natural API to work with, e.g. being able to
 treat tags as sets.  However it does mean that the
-:meth:`__contains__`, :meth:`__len__` and frieds methods on these are
-usually more and essentially O(n) rather than O(1) as you might
-usually expect from Python containers.
+:meth:`__contains__`, :meth:`__len__` and similar methods on these are
+usually more expensive and essentially O(n) rather than O(1) as you might
+expect from Python containers.
 """
 
 from notmuch2 import _capi
 """
 
 from notmuch2 import _capi
index 1cf03c8813e5b03dffbc069fda525aad411a7eba..1b0b5b7e1e1f0d0e350e97ee6e9cbed0a1595494 100644 (file)
@@ -28,7 +28,7 @@ class NotmuchObject(metaclass=abc.ABCMeta):
 
     However during some peculiar situations, e.g. interpreter
     shutdown, it is possible for the :meth:`__del__` method to have
 
     However during some peculiar situations, e.g. interpreter
     shutdown, it is possible for the :meth:`__del__` method to have
-    been called, whele there are still references to an object.  This
+    been called, while there are still references to an object.  This
     could result in child objects asking their memory to be freed
     after the parent has already freed the memory, making things
     rather unhappy as double frees are not taken lightly in C.  To
     could result in child objects asking their memory to be freed
     after the parent has already freed the memory, making things
     rather unhappy as double frees are not taken lightly in C.  To
@@ -167,14 +167,14 @@ class NotmuchIter(NotmuchObject, collections.abc.Iterator):
     It is tempting to use a generator function instead, but this would
     not correctly respect the :class:`NotmuchObject` memory handling
     protocol and in some unsuspecting cornercases cause memory
     It is tempting to use a generator function instead, but this would
     not correctly respect the :class:`NotmuchObject` memory handling
     protocol and in some unsuspecting cornercases cause memory
-    trouble.  You probably want to sublcass this in order to wrap the
+    trouble.  You probably want to subclass this in order to wrap the
     value returned by :meth:`__next__`.
 
     :param parent: The parent object.
     :type parent: NotmuchObject
     :param iter_p: The CFFI pointer to the C iterator.
     :type iter_p: cffi.cdata
     value returned by :meth:`__next__`.
 
     :param parent: The parent object.
     :type parent: NotmuchObject
     :param iter_p: The CFFI pointer to the C iterator.
     :type iter_p: cffi.cdata
-    :param fn_destory: The CFFI notmuch_*_destroy function.
+    :param fn_destroy: The CFFI notmuch_*_destroy function.
     :param fn_valid: The CFFI notmuch_*_valid function.
     :param fn_get: The CFFI notmuch_*_get function.
     :param fn_next: The CFFI notmuch_*_move_to_next function.
     :param fn_valid: The CFFI notmuch_*_valid function.
     :param fn_get: The CFFI notmuch_*_get function.
     :param fn_next: The CFFI notmuch_*_move_to_next function.
@@ -223,7 +223,6 @@ class NotmuchIter(NotmuchObject, collections.abc.Iterator):
 
     def __next__(self):
         if not self._fn_valid(self._iter_p):
 
     def __next__(self):
         if not self._fn_valid(self._iter_p):
-            self._destroy()
             raise StopIteration()
         obj_p = self._fn_get(self._iter_p)
         self._fn_next(self._iter_p)
             raise StopIteration()
         obj_p = self._fn_get(self._iter_p)
         self._fn_next(self._iter_p)
index 603fdcbf02c6b544e6224085df20f96c51ef00ee..4df03bd0841387aeb0499d006578997a79d4c75b 100644 (file)
@@ -35,7 +35,7 @@ class ConfigIter(base.NotmuchIter):
 
 class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
     """The config key/value pairs loaded from the database, config file,
 
 class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
     """The config key/value pairs loaded from the database, config file,
-    and and/or defaults.
+    and/or defaults.
 
     The entries are exposed as a :class:`collections.abc.MutableMapping` object.
     Note that setting a value to an empty string is the same as deleting it.
 
     The entries are exposed as a :class:`collections.abc.MutableMapping` object.
     Note that setting a value to an empty string is the same as deleting it.
index d7485b4d983d8f55efd92b8a8581dac86e415c3a..c13d9fcfe5321830a32bfcb5501f2a0ed585a92b 100644 (file)
@@ -15,7 +15,7 @@ import notmuch2._query as querymod
 import notmuch2._tags as tags
 
 
 import notmuch2._tags as tags
 
 
-__all__ = ['Database', 'AtomicContext', 'DbRevision']
+__all__ = ['Database', 'AtomicContext', 'DbRevision', 'IndexOptions']
 
 
 def _config_pathname():
 
 
 def _config_pathname():
@@ -351,7 +351,7 @@ class Database(base.NotmuchObject):
         The returned context manager can be used to perform atomic
         operations on the database.
 
         The returned context manager can be used to perform atomic
         operations on the database.
 
-        .. note:: Unlinke a traditional RDBMS transaction this does
+        .. note:: Unlike a traditional RDBMS transaction this does
            not imply durability, it only ensures the changes are
            performed atomically.
 
            not imply durability, it only ensures the changes are
            performed atomically.
 
@@ -416,7 +416,7 @@ class Database(base.NotmuchObject):
         :raises XapianError: A Xapian exception occurred.
         :raises FileError: The file referred to by ``pathname`` could
            not be opened.
         :raises XapianError: A Xapian exception occurred.
         :raises FileError: The file referred to by ``pathname`` could
            not be opened.
-        :raises FileNotEmailError: The file referreed to by
+        :raises FileNotEmailError: The file referred to by
            ``pathname`` is not recognised as an email message.
         :raises ReadOnlyDatabaseError: The database is opened in
            READ_ONLY mode.
            ``pathname`` is not recognised as an email message.
         :raises ReadOnlyDatabaseError: The database is opened in
            READ_ONLY mode.
@@ -833,7 +833,7 @@ class IndexOptions(base.NotmuchObject):
         """The decryption policy.
 
         This is an enum from the :class:`DecryptionPolicy`.  See the
         """The decryption policy.
 
         This is an enum from the :class:`DecryptionPolicy`.  See the
-        `index.decrypt` section in :man:`notmuch-config` for details
+        `index.decrypt` section in :any:`notmuch-config(1)` for details
         on the options.  **Do not set this to
         :attr:`DecryptionPolicy.TRUE`** without considering the
         security of your index.
         on the options.  **Do not set this to
         :attr:`DecryptionPolicy.TRUE`** without considering the
         security of your index.
index d4b34e91e2f88ff367a73cfa5b311d11cbfa6b9a..794852389fe70262e9502fe735733db41725fc6c 100644 (file)
@@ -80,12 +80,6 @@ class Message(base.NotmuchObject):
     def messageid(self):
         """The message ID as a string.
 
     def messageid(self):
         """The message ID as a string.
 
-        The message ID is decoded with the ignore error handler.  This
-        is fine as long as the message ID is well formed.  If it is
-        not valid ASCII then this will be lossy.  So if you need to be
-        able to write the exact same message ID back you should use
-        :attr:`messageidb`.
-
         Note that notmuch will decode the message ID value and thus
         strip off the surrounding ``<`` and ``>`` characters.  This is
         different from Python's :mod:`email` package behaviour which
         Note that notmuch will decode the message ID value and thus
         strip off the surrounding ``<`` and ``>`` characters.  This is
         different from Python's :mod:`email` package behaviour which
@@ -246,8 +240,7 @@ class Message(base.NotmuchObject):
         :param header: Case-insensitive header name to retrieve.
         :type header: str or bytes
 
         :param header: Case-insensitive header name to retrieve.
         :type header: str or bytes
 
-        :returns: The header value, an empty string if the header is
-           not present.
+        :returns: The header value.
         :rtype: str
 
         :raises LookupError: if the header is not present.
         :rtype: str
 
         :raises LookupError: if the header is not present.
@@ -349,9 +342,9 @@ class Message(base.NotmuchObject):
         and thus are only recommended if you know there to be only one
         value.
 
         and thus are only recommended if you know there to be only one
         value.
 
-        Instead the map has an additional :meth:`PropertiesMap.all`
+        Instead the map has an additional :meth:`PropertiesMap.getall`
         method which can be used to retrieve all properties of a given
         method which can be used to retrieve all properties of a given
-        key.  This method also allows iterating of a subset of the
+        key.  This method also allows iterating of a subset of the
         keys starting with a given prefix.
         """
         try:
         keys starting with a given prefix.
         """
         try:
@@ -435,7 +428,7 @@ class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping):
     """A mutable mapping to manage properties.
 
     Both keys and values of properties are supposed to be UTF-8
     """A mutable mapping to manage properties.
 
     Both keys and values of properties are supposed to be UTF-8
-    strings in libnotmuch.  However since the uderlying API uses
+    strings in libnotmuch.  However since the underlying API uses
     bytestrings you can use either str or bytes to represent keys and
     all returned keys and values use :class:`BinString`.
 
     bytestrings you can use either str or bytes to represent keys and
     all returned keys and values use :class:`BinString`.
 
@@ -493,7 +486,7 @@ class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping):
         return len(list(it))
 
     def __getitem__(self, key):
         return len(list(it))
 
     def __getitem__(self, key):
-        """Return **the first** peroperty associated with a key."""
+        """Return **the first** property associated with a key."""
         if isinstance(key, str):
             key = key.encode('utf-8')
         value_pp = capi.ffi.new('char**')
         if isinstance(key, str):
             key = key.encode('utf-8')
         value_pp = capi.ffi.new('char**')
@@ -516,7 +509,7 @@ class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping):
         """Return a :class:`collections.abc.ItemsView` for this map.
 
         The ItemsView treats a ``(key, value)`` pair as unique, so
         """Return a :class:`collections.abc.ItemsView` for this map.
 
         The ItemsView treats a ``(key, value)`` pair as unique, so
-        dupcliate ``(key, value)`` pairs will be merged together.
+        duplicate ``(key, value)`` pairs will be merged together.
         However duplicate keys with different values will be returned.
         """
         items = set()
         However duplicate keys with different values will be returned.
         """
         items = set()
@@ -531,7 +524,7 @@ class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping):
         return PropertiesItemsView(items)
 
     def values(self):
         return PropertiesItemsView(items)
 
     def values(self):
-        """Return a :class:`collecions.abc.ValuesView` for this map.
+        """Return a :class:`collections.abc.ValuesView` for this map.
 
         All unique property values are included in the view.
         """
 
         All unique property values are included in the view.
         """
@@ -610,7 +603,7 @@ class PropertiesMap(base.NotmuchObject, collections.abc.MutableMapping):
     def getall(self, prefix='', *, exact=False):
         """Return an iterator yielding all properties for a given key prefix.
 
     def getall(self, prefix='', *, exact=False):
         """Return an iterator yielding all properties for a given key prefix.
 
-        The returned iterator yields all peroperties which start with
+        The returned iterator yields all properties which start with
         a given key prefix as ``(key, value)`` namedtuples.  If called
         with ``exact=True`` then only properties which exactly match
         the prefix are returned, those a key longer than the prefix
         a given key prefix as ``(key, value)`` namedtuples.  If called
         with ``exact=True`` then only properties which exactly match
         the prefix are returned, those a key longer than the prefix
@@ -655,7 +648,6 @@ class PropertiesIter(base.NotmuchIter):
 
     def __next__(self):
         if not self._fn_valid(self._iter_p):
 
     def __next__(self):
         if not self._fn_valid(self._iter_p):
-            self._destroy()
             raise StopIteration
         key = capi.lib.notmuch_message_properties_key(self._iter_p)
         value = capi.lib.notmuch_message_properties_value(self._iter_p)
             raise StopIteration
         key = capi.lib.notmuch_message_properties_key(self._iter_p)
         value = capi.lib.notmuch_message_properties_value(self._iter_p)
index 1db6ec966b79859618b327094feea4fb921fe59f..169fcf272dcef15e7879aa92166f4b2195506b85 100644 (file)
@@ -52,7 +52,7 @@ class Query(base.NotmuchObject):
         This executes the query and returns an iterator over the
         :class:`Message` objects found.
         """
         This executes the query and returns an iterator over the
         :class:`Message` objects found.
         """
-        msgs_pp = capi.ffi.new('notmuch_messages_t**')
+        msgs_pp = capi.ffi.new('notmuch_messages_t **')
         ret = capi.lib.notmuch_query_search_messages(self._query_p, msgs_pp)
         if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
             raise errors.NotmuchError(ret)
         ret = capi.lib.notmuch_query_search_messages(self._query_p, msgs_pp)
         if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
             raise errors.NotmuchError(ret)
index ee5d2a34b2c210540b131539c3b0d159ee942a82..28528831530b96c641ff78ed3128df6e65cf5815 100644 (file)
@@ -69,7 +69,7 @@ class ImmutableTagSet(base.NotmuchObject, collections.abc.Set):
         return self.iter(encoding='utf-8', errors='ignore')
 
     def iter(self, *, encoding=None, errors='strict'):
         return self.iter(encoding='utf-8', errors='ignore')
 
     def iter(self, *, encoding=None, errors='strict'):
-        """Aternate iterator constructor controlling string decoding.
+        """Alternate iterator constructor controlling string decoding.
 
         Tags are stored as bytes in the notmuch database, in Python
         it's easier to work with unicode strings and thus is what the
 
         Tags are stored as bytes in the notmuch database, in Python
         it's easier to work with unicode strings and thus is what the
@@ -87,7 +87,7 @@ class ImmutableTagSet(base.NotmuchObject, collections.abc.Set):
         """
         # self._cffi_fn should point either to
         # notmuch_database_get_all_tags, notmuch_thread_get_tags or
         """
         # self._cffi_fn should point either to
         # notmuch_database_get_all_tags, notmuch_thread_get_tags or
-        # notmuch_message_get_tags.  nothmuch.h suggests these never
+        # notmuch_message_get_tags.  notmuch.h suggests these never
         # fail, let's handle NULL anyway.
         tags_p = self._cffi_fn(self._ptr())
         if tags_p == capi.ffi.NULL:
         # fail, let's handle NULL anyway.
         tags_p = self._cffi_fn(self._ptr())
         if tags_p == capi.ffi.NULL:
@@ -155,7 +155,7 @@ class MutableTagSet(ImmutableTagSet, collections.abc.MutableSet):
     Tags are bytestrings and calling ``iter()`` will return an
     iterator yielding bytestrings.  However the :meth:`iter` method
     can be used to return tags as unicode strings, while all other
     Tags are bytestrings and calling ``iter()`` will return an
     iterator yielding bytestrings.  However the :meth:`iter` method
     can be used to return tags as unicode strings, while all other
-    operations accept either byestrings or unicode strings.  In case
+    operations accept either bytestrings or unicode strings.  In case
     unicode strings are used they will be encoded using utf-8 before
     being passed to notmuch.
     """
     unicode strings are used they will be encoded using utf-8 before
     being passed to notmuch.
     """
index 55fb2d24f832b49e2730db879309b441a58a0a78..3719c86cb7a6472363195855ed1fda7fa39efc1e 100644 (file)
@@ -2,7 +2,7 @@ import setuptools
 from _notmuch_config import *
 
 with open(NOTMUCH_VERSION_FILE) as fp:
 from _notmuch_config import *
 
 with open(NOTMUCH_VERSION_FILE) as fp:
-    VERSION = fp.read().strip()
+    VERSION = fp.read().strip().replace('~', '')
 
 setuptools.setup(
     name='notmuch2',
 
 setuptools.setup(
     name='notmuch2',
index f1d12ea6c2f046186377a7ce48b104d3a5855873..1557235d76ea7b354dcc53042964a1ad2b7f2368 100644 (file)
@@ -303,6 +303,18 @@ class TestQuery:
         msgs = db.messages('*')
         assert isinstance(msgs, collections.abc.Iterator)
 
         msgs = db.messages('*')
         assert isinstance(msgs, collections.abc.Iterator)
 
+    def test_messages_iterator(self, db):
+        for msg in db.messages('*'):
+            assert isinstance(msg, notmuch2.Message)
+            assert isinstance(msg.messageid, str)
+
+    def test_messages_iterator_list(self, db):
+        msgs = list(db.messages('*'))
+        assert len(msgs) == 3
+        for msg in msgs:
+            assert isinstance(msg, notmuch2.Message)
+            assert isinstance(msg.messageid, str)
+
     def test_message_no_results(self, db):
         msgs = db.messages('not_a_matching_query')
         with pytest.raises(StopIteration):
     def test_message_no_results(self, db):
         msgs = db.messages('not_a_matching_query')
         with pytest.raises(StopIteration):
@@ -320,6 +332,25 @@ class TestQuery:
         threads = db.threads('*')
         assert isinstance(threads, collections.abc.Iterator)
 
         threads = db.threads('*')
         assert isinstance(threads, collections.abc.Iterator)
 
+    def test_threads_iterator(self, db):
+        for t in db.threads('*'):
+            assert isinstance(t, notmuch2.Thread)
+            assert isinstance(t.threadid, str)
+            for msg in t:
+                assert isinstance(msg, notmuch2.Message)
+                assert isinstance(msg.messageid, str)
+
+    def test_threads_iterator_list(self, db):
+        threads = list(db.threads('*'))
+        assert len(threads) == 2
+        for t in threads:
+            assert isinstance(t, notmuch2.Thread)
+            assert isinstance(t.threadid, str)
+            msgs = list(t)
+            for msg in msgs:
+                assert isinstance(msg, notmuch2.Message)
+                assert isinstance(msg.messageid, str)
+
     def test_threads_no_match(self, db):
         threads = db.threads('not_a_matching_query')
         with pytest.raises(StopIteration):
     def test_threads_no_match(self, db):
         threads = db.threads('not_a_matching_query')
         with pytest.raises(StopIteration):
index 56701d056de5ff6d1cda5e7fc0c53b1cdd8a6de5..3b103530922e8adac28a7902fe58114ee4f5309d 100644 (file)
@@ -218,6 +218,20 @@ class TestProperties:
         props.add('foo', 'a')
         assert set(props.getall('foo')) == {('foo', 'a')}
 
         props.add('foo', 'a')
         assert set(props.getall('foo')) == {('foo', 'a')}
 
+    def test_getall_iter(self, props):
+        props.add('foo', 'a')
+        props.add('foobar', 'b')
+        for prop in props.getall('foo'):
+            assert isinstance(prop.value, str)
+
+    def test_getall_iter_list(self, props):
+        props.add('foo', 'a')
+        props.add('foobar', 'b')
+        res = list(props.getall('foo'))
+        assert len(res) == 2
+        for prop in res:
+            assert isinstance(prop.value, str)
+
     def test_getall_prefix(self, props):
         props.add('foo', 'a')
         props.add('foobar', 'b')
     def test_getall_prefix(self, props):
         props.add('foo', 'a')
         props.add('foobar', 'b')
diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore
deleted file mode 100644 (file)
index 601acdd..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-*.py[co]
-/docs/build
-/docs/html
-/build/
diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in
deleted file mode 100644 (file)
index c83be4b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-include notmuch
-#recursive-include docs/html *
\ No newline at end of file
diff --git a/bindings/python/README b/bindings/python/README
deleted file mode 100644 (file)
index 5bf076d..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-notmuch -- The python interface to notmuch
-==========================================
-
-This module makes the functionality of the notmuch library
-(`https://notmuchmail.org`_) available to python. Successful import of
-this module depends on a libnotmuch.so|dll being available on the
-user's system.
-
-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.io/projects/notmuch-python/
-
-To build the python bindings, do
-
-  python setup.py install --prefix=path/to/your/preferred/location
diff --git a/bindings/python/docs/COPYING b/bindings/python/docs/COPYING
deleted file mode 100644 (file)
index e600086..0000000
+++ /dev/null
@@ -1,674 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- 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.
-
-                            Preamble
-
-  The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.  We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors.  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, 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
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights.  Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received.  You must make sure that they, too, receive
-or can get the source code.  And you must show them these terms so they
-know their rights.
-
-  Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-  For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software.  For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-  Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so.  This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software.  The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable.  Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products.  If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-  Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary.  To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If 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 convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Use with the GNU Affero General Public License.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU 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
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state 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 program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    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/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
-    <program>  Copyright (C) <year>  <name of author>
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-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
-<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
-<https://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/bindings/python/docs/Makefile b/bindings/python/docs/Makefile
deleted file mode 100644 (file)
index bd6de38..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
-
-.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
-
-help:
-       @echo "Please use \`make <target>' where <target> is one of"
-       @echo "  html      to make standalone HTML files"
-       @echo "  dirhtml   to make HTML files named index.html in directories"
-       @echo "  pickle    to make pickle files"
-       @echo "  json      to make JSON files"
-       @echo "  htmlhelp  to make HTML files and a HTML help project"
-       @echo "  qthelp    to make HTML files and a qthelp project"
-       @echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-       @echo "  changes   to make an overview of all changed/added/deprecated items"
-       @echo "  linkcheck to check all external links for integrity"
-       @echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
-
-clean:
-       -rm -rf build/*
-
-html:
-       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html
-       @echo
-       @echo "Build finished. The HTML pages are in html."
-
-dirhtml:
-       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml
-       @echo
-       @echo "Build finished. The HTML pages are in build/dirhtml."
-
-pickle:
-       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
-       @echo
-       @echo "Build finished; now you can process the pickle files."
-
-json:
-       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json
-       @echo
-       @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
-       @echo
-       @echo "Build finished; now you can run HTML Help Workshop with the" \
-             ".hhp project file in build/htmlhelp."
-
-qthelp:
-       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp
-       @echo
-       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
-             ".qhcp project file in build/qthelp, like this:"
-       @echo "# qcollectiongenerator build/qthelp/pyDNS.qhcp"
-       @echo "To view the help file:"
-       @echo "# assistant -collectionFile build/qthelp/pyDNS.qhc"
-
-latex:
-       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
-       @echo
-       @echo "Build finished; the LaTeX files are in build/latex."
-       @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
-             "run these through (pdf)latex."
-
-changes:
-       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
-       @echo
-       @echo "The overview file is in build/changes."
-
-linkcheck:
-       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
-       @echo
-       @echo "Link check complete; look for any errors in the above output " \
-             "or in build/linkcheck/output.txt."
-
-doctest:
-       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest
-       @echo "Testing of doctests in the sources finished, look at the " \
-             "results in build/doctest/output.txt."
diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py
deleted file mode 100644 (file)
index 8b43c5c..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# pyDNS documentation build configuration file, created by
-# sphinx-quickstart on Tue Feb  2 10:00:47 2010.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-from unittest.mock import Mock
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0,os.path.abspath('../..'))
-
-MOCK_MODULES = [
-    'ctypes',
-]
-for mod_name in MOCK_MODULES:
-    sys.modules[mod_name] = Mock()
-
-
-from notmuch import __VERSION__,__AUTHOR__
-# -- General configuration -----------------------------------------------------
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo']
-autoclass_content = "both"
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'notmuch'
-copyright = u'2010-2012, ' + __AUTHOR__
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = __VERSION__
-# The full version, including alpha/beta/rc tags.
-release = __VERSION__
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of documents that shouldn't be included in the build.
-#unused_docs = []
-
-# List of directories, relative to source directory, that shouldn't be searched
-# for source files.
-exclude_trees = []
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-add_module_names = False
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  Major themes that come with
-# Sphinx are currently 'default' and 'sphinxdoc'.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = []
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-html_use_modindex = False
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'notmuchdoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
-  ('index', 'notmuch.tex', u'notmuch Documentation',
-   u'notmuch contributors', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_use_modindex = True
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/bindings/python/docs/source/database.rst b/bindings/python/docs/source/database.rst
deleted file mode 100644 (file)
index 660de91..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-:class:`Database` -- The underlying notmuch database
-====================================================
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]])
-
-   .. automethod:: create
-
-   .. automethod:: open(path, status=MODE.READ_ONLY)
-
-   .. automethod:: close
-
-   .. automethod:: get_path
-
-   .. automethod:: get_version
-
-   .. automethod:: needs_upgrade
-
-   .. automethod:: upgrade
-
-   .. automethod:: begin_atomic
-
-   .. automethod:: end_atomic
-
-   .. automethod:: get_directory
-
-   .. automethod:: index_file
-
-   .. automethod:: remove_message
-
-   .. automethod:: find_message
-
-   .. automethod:: find_message_by_filename
-
-   .. automethod:: get_all_tags
-
-   .. automethod:: create_query
-
-   .. automethod:: get_config
-
-   .. automethod:: get_configs
-
-   .. automethod:: set_config
-
-   .. attribute:: Database.MODE
-
-     Defines constants that are used as the mode in which to open a database.
-
-     MODE.READ_ONLY
-       Open the database in read-only mode
-
-     MODE.READ_WRITE
-       Open the database in read-write mode
diff --git a/bindings/python/docs/source/filesystem.rst b/bindings/python/docs/source/filesystem.rst
deleted file mode 100644 (file)
index 13fe119..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-Files and directories
-=====================
-
-.. currentmodule:: notmuch
-
-:class:`Filenames` -- An iterator over filenames
-------------------------------------------------
-
-.. autoclass:: Filenames
-
-   .. method:: Filenames.__len__
-   .. warning::
-      :meth:`__len__` was removed in version 0.22 as it exhausted the
-      iterator and broke list(Filenames()). Use `len(list(names))`
-      instead.
-
-:class:`Directory` -- A directory entry in the database
--------------------------------------------------------
-
-.. autoclass:: Directory
-
-   .. automethod:: Directory.get_child_files
-
-   .. automethod:: Directory.get_child_directories
-
-   .. automethod:: Directory.get_mtime
-
-   .. automethod:: Directory.set_mtime
-
-   .. autoattribute:: Directory.mtime
-
-   .. autoattribute:: Directory.path
diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst
deleted file mode 100644 (file)
index bef7e60..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-Welcome to :mod:`notmuch`'s documentation
-=========================================
-
-.. currentmodule:: notmuch
-
-The :mod:`notmuch` module provides an interface to the `notmuch
-<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
-:class:`Tags`.
-
-.. moduleauthor:: Sebastian Spaeth <Sebastian@SSpaeth.de>
-
-:License: This module is covered under the GNU GPL v3 (or later).
-
-.. toctree::
-   :maxdepth: 1
-
-   quickstart
-   notes
-   status_and_errors
-   database
-   query
-   messages
-   message
-   tags
-   threads
-   thread
-   filesystem
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`search`
diff --git a/bindings/python/docs/source/message.rst b/bindings/python/docs/source/message.rst
deleted file mode 100644 (file)
index b003392..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-:class:`Message` -- A single message
-====================================
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Message
-
-   .. automethod:: get_message_id
-
-   .. automethod:: get_thread_id
-
-   .. automethod:: get_replies
-
-   .. automethod:: get_filename
-
-   .. automethod:: get_filenames
-
-   .. attribute:: FLAG
-
-        FLAG.MATCH
-          This flag is automatically set by a
-         Query.search_threads on those messages that match the
-         query. This allows us to distinguish matches from the rest
-         of the messages in that thread.
-
-   .. automethod:: get_flag
-
-   .. automethod:: set_flag
-
-   .. automethod:: get_date
-
-   .. automethod:: get_header
-
-   .. automethod:: get_tags
-
-   .. automethod:: get_property
-
-   .. automethod:: get_properties
-
-   .. automethod:: maildir_flags_to_tags
-
-   .. automethod:: tags_to_maildir_flags
-
-   .. automethod:: remove_tag
-
-   .. automethod:: add_tag
-
-   .. automethod:: remove_all_tags
-
-   .. automethod:: freeze
-
-   .. automethod:: thaw
-
-   .. automethod:: __str__
diff --git a/bindings/python/docs/source/messages.rst b/bindings/python/docs/source/messages.rst
deleted file mode 100644 (file)
index 3ccf505..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-:class:`Messages` -- A bunch of messages
-========================================
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Messages
-
-   .. automethod:: collect_tags
-
-   .. method:: __len__()
-
-   .. warning::
-
-      :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
-      list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`.
diff --git a/bindings/python/docs/source/notes.rst b/bindings/python/docs/source/notes.rst
deleted file mode 100644 (file)
index a792748..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Interfacing with notmuch
-========================
-
-.. todo:: move the note about talloc out of the code
-
-.. automodule:: notmuch
diff --git a/bindings/python/docs/source/query.rst b/bindings/python/docs/source/query.rst
deleted file mode 100644 (file)
index 785e984..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-:class:`Query` -- A search query
-================================
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Query
-
-   .. automethod:: create
-
-   .. attribute:: Query.SORT
-
-     Defines constants that are used as the mode in which to open a database.
-
-     SORT.OLDEST_FIRST
-       Sort by message date, oldest first.
-
-     SORT.NEWEST_FIRST
-       Sort by message date, newest first.
-
-     SORT.MESSAGE_ID
-       Sort by email message ID.
-
-     SORT.UNSORTED
-       Do not apply a special sort order (returns results in document id
-       order).
-
-   .. automethod:: set_sort
-
-   .. attribute::  sort
-
-      Instance attribute :attr:`sort` contains the sort order (see
-      :attr:`Query.SORT`) if explicitly specified via
-      :meth:`set_sort`. By default it is set to `None`.
-
-   .. automethod:: exclude_tag
-
-   .. automethod:: search_threads
-
-   .. automethod:: search_messages
-
-   .. automethod:: count_messages
-
-   .. automethod:: count_threads
diff --git a/bindings/python/docs/source/quickstart.rst b/bindings/python/docs/source/quickstart.rst
deleted file mode 100644 (file)
index 609f42e..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-Quickstart and examples
-=======================
-
-.. todo:: write a nice introduction
-.. todo:: improve the examples
-
-Notmuch can be imported as::
-
-    import notmuch
-
-or::
-
-    from notmuch import Query, Database
-
-    db = Database('path', create=True)
-    msgs = Query(db, 'from:myself').search_messages()
-
-    for msg in msgs:
-        print(msg)
diff --git a/bindings/python/docs/source/status_and_errors.rst b/bindings/python/docs/source/status_and_errors.rst
deleted file mode 100644 (file)
index 68913f1..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-.. currentmodule:: notmuch
-
-Status and Errors
-=================
-
-Some methods return a status, indicating if an operation was successful and what the error was. Most of these status codes are expressed as a specific value, the :class:`notmuch.STATUS`.
-
-.. note::
-
-    Prior to version 0.12 the exception classes and the enumeration
-    :class:`notmuch.STATUS` were defined in `notmuch.globals`. They
-    have since then been moved into `notmuch.errors`.
-
-:class:`STATUS` -- Notmuch operation return value
---------------------------------------------------
-
-.. autoclass:: notmuch.STATUS
-   :inherited-members:
-
-.. automethod:: notmuch.STATUS.status2str
-
-:exc:`NotmuchError` -- A Notmuch execution error
-------------------------------------------------
-Whenever an error occurs, we throw a special Exception :exc:`NotmuchError`, or a more fine grained Exception which is derived from it. This means it is always safe to check for NotmuchErrors if you want to catch all errors. If you are interested in more fine grained exceptions, you can use those below.
-
-.. autoexception:: NotmuchError
-
-The following exceptions are all directly derived from NotmuchError. Each of them corresponds to a specific :class:`notmuch.STATUS` value. You can either check the :attr:`status` attribute of a NotmuchError to see if a specific error has occurred, or you can directly check for the following Exception types:
-
-.. autoexception:: OutOfMemoryError(message=None)
-   :members:
-.. autoexception:: ReadOnlyDatabaseError(message=None)
-   :members:
-.. autoexception:: XapianError(message=None)
-   :members:
-.. autoexception:: FileError(message=None)
-   :members:
-.. autoexception:: FileNotEmailError(message=None)
-   :members:
-.. autoexception:: DuplicateMessageIdError(message=None)
-   :members:
-.. autoexception:: NullPointerError(message=None)
-   :members:
-.. autoexception:: TagTooLongError(message=None)
-   :members:
-.. autoexception:: UnbalancedFreezeThawError(message=None)
-   :members:
-.. autoexception:: UnbalancedAtomicError(message=None)
-   :members:
-.. autoexception:: UnsupportedOperationError(message=None)
-   :members:
-.. autoexception:: UpgradeRequiredError(message=None)
-   :members:
-.. autoexception:: PathError(message=None)
-   :members:
-.. autoexception:: NotInitializedError(message=None)
-   :members:
diff --git a/bindings/python/docs/source/tags.rst b/bindings/python/docs/source/tags.rst
deleted file mode 100644 (file)
index 31527d4..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-:class:`Tags` -- Notmuch tags
------------------------------
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Tags
-   :members:
-
-   .. method:: __len__
-
-       .. warning::
-
-            :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
-            list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of
-            tags.
-
-   .. automethod:: __str__
diff --git a/bindings/python/docs/source/thread.rst b/bindings/python/docs/source/thread.rst
deleted file mode 100644 (file)
index 4067872..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-:class:`Thread` -- A single thread
-==================================
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Thread
-
-  .. automethod:: get_thread_id
-
-  .. automethod:: get_total_messages
-
-  .. automethod:: get_toplevel_messages
-
-  .. automethod:: get_matched_messages
-
-  .. automethod:: get_authors
-
-  .. automethod:: get_subject
-
-  .. automethod:: get_oldest_date
-
-  .. automethod:: get_newest_date
-
-  .. automethod:: get_tags
-
-  .. automethod:: __str__
diff --git a/bindings/python/docs/source/threads.rst b/bindings/python/docs/source/threads.rst
deleted file mode 100644 (file)
index 46ce5be..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-:class:`Threads` -- Threads iterator
-====================================
-
-.. currentmodule:: notmuch
-
-.. autoclass:: Threads
-
-   .. method:: __len__
-   .. warning::
-      :meth:`__len__` was removed in version 0.22 as it exhausted the
-      iterator and broke list(Threads()). Use `len(list(msgs))`
-      instead.
-
-   .. automethod:: __str__
diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py
deleted file mode 100644 (file)
index cf627ff..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-"""The :mod:`notmuch` module provides most of the functionality that a user is
-likely to need.
-
-.. note:: The underlying notmuch library is build on a hierarchical
-    memory allocator called talloc. All objects derive from a
-    top-level :class:`Database` object.
-
-    This means that as soon as an object is deleted, all underlying
-    derived objects such as Queries, Messages, Message, and Tags will
-    be freed by the underlying library as well. Accessing these
-    objects will then lead to segfaults and other unexpected behavior.
-
-    We implement reference counting, so that parent objects can be
-    automatically freed when they are not needed anymore. For
-    example::
-
-            db = Database('path',create=True)
-            msgs = Query(db,'from:myself').search_messages()
-
-    This returns :class:`Messages` which internally contains a
-    reference to its parent :class:`Query` object. Otherwise the
-    Query() would be immediately freed, taking our *msgs* down with
-    it.
-
-    In this case, the above Query() object will be automatically freed
-    whenever we delete all derived objects, ie in our case:
-    `del(msgs)` would also delete the parent Query. It would not
-    delete the parent Database() though, as that is still referenced
-    from the variable *db* in which it is stored.
-
-    Pretty much the same is valid for all other objects in the
-    hierarchy, such as :class:`Query`, :class:`Messages`,
-    :class:`Message`, and :class:`Tags`.
-"""
-
-"""
-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 <https://www.gnu.org/licenses/>.
-
-Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-from .database import Database
-from .directory import Directory
-from .filenames import Filenames
-from .message import Message
-from .messages import Messages
-from .query import Query
-from .tag import Tags
-from .thread import Thread
-from .threads import Threads
-from .globals import nmlib
-from .errors import (
-    STATUS,
-    NotmuchError,
-    OutOfMemoryError,
-    ReadOnlyDatabaseError,
-    XapianError,
-    FileError,
-    FileNotEmailError,
-    DuplicateMessageIdError,
-    NullPointerError,
-    TagTooLongError,
-    UnbalancedFreezeThawError,
-    UnbalancedAtomicError,
-    NotInitializedError,
-    UnsupportedOperationError,
-    UpgradeRequiredError,
-    PathError,
-)
-from .version import __VERSION__
-__LICENSE__ = "GPL v3+"
-__AUTHOR__ = 'Sebastian Spaeth <Sebastian@SSpaeth.de>'
diff --git a/bindings/python/notmuch/compat.py b/bindings/python/notmuch/compat.py
deleted file mode 100644 (file)
index c931329..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-'''
-This file is part of notmuch.
-
-This module handles differences between python2.x and python3.x and
-allows the notmuch bindings to support both version families with one
-source tree.
-
-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 <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-Copyright 2012 Justus Winter <4winter@informatik.uni-hamburg.de>
-'''
-
-import sys
-
-if sys.version_info[0] == 2:
-    from ConfigParser import SafeConfigParser
-
-    class Python3StringMixIn(object):
-        def __str__(self):
-            return unicode(self).encode('utf-8')
-
-    def encode_utf8(value):
-        '''
-        Ensure a nicely utf-8 encoded string to pass to wrapped
-        libnotmuch functions.
-
-        C++ code expects strings to be well formatted and unicode
-        strings to have no null bytes.
-        '''
-        if not isinstance(value, basestring):
-            raise TypeError('Expected str or unicode, got %s' % type(value))
-
-        if isinstance(value, unicode):
-            return value.encode('utf-8', 'replace')
-
-        return value
-else:
-    from configparser import SafeConfigParser
-
-    class Python3StringMixIn(object):
-        def __str__(self):
-            return self.__unicode__()
-
-    def encode_utf8(value):
-        '''
-        Ensure a nicely utf-8 encoded string to pass to wrapped
-        libnotmuch functions.
-
-        C++ code expects strings to be well formatted and unicode
-        strings to have no null bytes.
-        '''
-        if not isinstance(value, str):
-            raise TypeError('Expected str, got %s' % type(value))
-
-        return value.encode('utf-8', 'replace')
-
-# We import the SafeConfigParser class on behalf of other code to cope
-# with the differences between Python 2 and 3.
-SafeConfigParser # avoid warning about unused import
diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py
deleted file mode 100644 (file)
index 8fb507f..0000000
+++ /dev/null
@@ -1,789 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-import os
-import codecs
-import warnings
-from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
-from .compat import SafeConfigParser
-from .globals import (
-    nmlib,
-    Enum,
-    _str,
-    NotmuchConfigListP,
-    NotmuchDatabaseP,
-    NotmuchDirectoryP,
-    NotmuchIndexoptsP,
-    NotmuchMessageP,
-    NotmuchTagsP,
-)
-from .errors import (
-    STATUS,
-    FileError,
-    NotmuchError,
-    NullPointerError,
-    NotInitializedError,
-)
-from .message import Message
-from .tag import Tags
-from .query import Query
-from .directory import Directory
-
-class Database(object):
-    """The :class:`Database` is the highest-level object that notmuch
-    provides. It references a notmuch database, and can be opened in
-    read-only or read-write mode. A :class:`Query` can be derived from
-    or be applied to a specific database to find messages. Also adding
-    and removing messages to the database happens via this
-    object. Modifications to the database are not atmic by default (see
-    :meth:`begin_atomic`) and once a database has been modified, all
-    other database objects pointing to the same data-base will throw an
-    :exc:`XapianError` as the underlying database has been
-    modified. Close and reopen the database to continue working with it.
-
-    :class:`Database` objects implement the context manager protocol
-    so you can use the :keyword:`with` statement to ensure that the
-    database is properly closed. See :meth:`close` for more
-    information.
-
-    .. note::
-
-        Any function in this class can and will throw an
-        :exc:`NotInitializedError` if the database was not initialized
-        properly.
-    """
-    _std_db_path = None
-    """Class attribute to cache user's default database"""
-
-    MODE = Enum(['READ_ONLY', 'READ_WRITE'])
-    """Constants: Mode in which to open the database"""
-
-    DECRYPTION_POLICY = Enum(['FALSE', 'TRUE', 'AUTO', 'NOSTASH'])
-    """Constants: policies for decrypting messages during indexing"""
-
-    """notmuch_database_get_directory"""
-    _get_directory = nmlib.notmuch_database_get_directory
-    _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)]
-    _get_directory.restype = c_uint
-
-    """notmuch_database_get_path"""
-    _get_path = nmlib.notmuch_database_get_path
-    _get_path.argtypes = [NotmuchDatabaseP]
-    _get_path.restype = c_char_p
-
-    """notmuch_database_get_version"""
-    _get_version = nmlib.notmuch_database_get_version
-    _get_version.argtypes = [NotmuchDatabaseP]
-    _get_version.restype = c_uint
-
-    """notmuch_database_get_revision"""
-    _get_revision = nmlib.notmuch_database_get_revision
-    _get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)]
-    _get_revision.restype = c_uint
-
-    """notmuch_database_open"""
-    _open = nmlib.notmuch_database_open
-    _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)]
-    _open.restype = c_uint
-
-    """notmuch_database_upgrade"""
-    _upgrade = nmlib.notmuch_database_upgrade
-    _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p]
-    _upgrade.restype = c_uint
-
-    """ notmuch_database_find_message"""
-    _find_message = nmlib.notmuch_database_find_message
-    _find_message.argtypes = [NotmuchDatabaseP, c_char_p,
-                              POINTER(NotmuchMessageP)]
-    _find_message.restype = c_uint
-
-    """notmuch_database_find_message_by_filename"""
-    _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
-    _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p,
-                                          POINTER(NotmuchMessageP)]
-    _find_message_by_filename.restype = c_uint
-
-    """notmuch_database_get_all_tags"""
-    _get_all_tags = nmlib.notmuch_database_get_all_tags
-    _get_all_tags.argtypes = [NotmuchDatabaseP]
-    _get_all_tags.restype = NotmuchTagsP
-
-    """notmuch_database_create"""
-    _create = nmlib.notmuch_database_create
-    _create.argtypes = [c_char_p, POINTER(NotmuchDatabaseP)]
-    _create.restype = c_uint
-
-    def __init__(self, path = None, create = False,
-                 mode = MODE.READ_ONLY):
-        """If *path* is `None`, we will try to read a users notmuch
-        configuration and use his configured database. The location of the
-        configuration file can be specified through the environment variable
-        *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
-
-        If *create* is `True`, the database will always be created in
-        :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
-
-        :param path:   Directory to open/create the database in (see
-                       above for behavior if `None`)
-        :type path:    `str` or `None`
-        :param create: Pass `False` to open an existing, `True` to create a new
-                       database.
-        :type create:  bool
-        :param mode:   Mode to open a database in. Is always
-                       :attr:`MODE`.READ_WRITE when creating a new one.
-        :type mode:    :attr:`MODE`
-        :raises: :exc:`NotmuchError` or derived exception in case of
-            failure.
-        """
-        self._db = None
-        self.mode = mode
-        if path is None:
-            # no path specified. use a user's default database
-            if Database._std_db_path is None:
-                #the following line throws a NotmuchError if it fails
-                Database._std_db_path = self._get_user_default_db()
-            path = Database._std_db_path
-
-        if create == False:
-            self.open(path, mode)
-        else:
-            self.create(path)
-
-    _destroy = nmlib.notmuch_database_destroy
-    _destroy.argtypes = [NotmuchDatabaseP]
-    _destroy.restype = c_uint
-
-    def __del__(self):
-        if self._db:
-            status = self._destroy(self._db)
-            if status != STATUS.SUCCESS:
-                raise NotmuchError(status)
-
-    def _assert_db_is_initialized(self):
-        """Raises :exc:`NotInitializedError` if self._db is `None`"""
-        if not self._db:
-            raise NotInitializedError()
-
-    def create(self, path):
-        """Creates a new notmuch database
-
-        This function is used by __init__() and usually does not need
-        to be called directly. It wraps the underlying
-        *notmuch_database_create* function and creates a new notmuch
-        database at *path*. It will always return a database in :attr:`MODE`
-        .READ_WRITE mode as creating an empty database for
-        reading only does not make a great deal of sense.
-
-        :param path: A directory in which we should create the database.
-        :type path: str
-        :raises: :exc:`NotmuchError` in case of any failure
-                    (possibly after printing an error message on stderr).
-        """
-        if self._db:
-            raise NotmuchError(message="Cannot create db, this Database() "
-                                       "already has an open one.")
-
-        db = NotmuchDatabaseP()
-        status = Database._create(_str(path), byref(db))
-
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        self._db = db
-        return status
-
-    def open(self, path, mode=0):
-        """Opens an existing database
-
-        This function is used by __init__() and usually does not need
-        to be called directly. It wraps the underlying
-        *notmuch_database_open* function.
-
-        :param status: Open the database in read-only or read-write mode
-        :type status:  :attr:`MODE`
-        :raises: Raises :exc:`NotmuchError` in case of any failure
-                    (possibly after printing an error message on stderr).
-        """
-        db = NotmuchDatabaseP()
-        status = Database._open(_str(path), mode, byref(db))
-
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        self._db = db
-        return status
-
-    _close = nmlib.notmuch_database_close
-    _close.argtypes = [NotmuchDatabaseP]
-    _close.restype = c_uint
-
-    def close(self):
-        '''
-        Closes the notmuch database.
-
-        .. warning::
-
-            This function closes the notmuch database. From that point
-            on every method invoked on any object ever derived from
-            the closed database may cease to function and raise a
-            NotmuchError.
-        '''
-        if self._db:
-            status = self._close(self._db)
-            if status != STATUS.SUCCESS:
-                raise NotmuchError(status)
-
-    def __enter__(self):
-        '''
-        Implements the context manager protocol.
-        '''
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):
-        '''
-        Implements the context manager protocol.
-        '''
-        self.close()
-
-    def get_path(self):
-        """Returns the file path of an open database"""
-        self._assert_db_is_initialized()
-        return Database._get_path(self._db).decode('utf-8')
-
-    def get_version(self):
-        """Returns the database format version
-
-        :returns: The database version as positive integer
-        """
-        self._assert_db_is_initialized()
-        return Database._get_version(self._db)
-
-    def get_revision (self):
-        """Returns the committed database revision and UUID
-
-        :returns: (revision, uuid) The database revision as a positive integer
-        and the UUID of the database.
-        """
-        self._assert_db_is_initialized()
-        uuid = c_char_p ()
-        revision = Database._get_revision(self._db, byref (uuid))
-        return (revision, uuid.value.decode ('utf-8'))
-
-    _needs_upgrade = nmlib.notmuch_database_needs_upgrade
-    _needs_upgrade.argtypes = [NotmuchDatabaseP]
-    _needs_upgrade.restype = bool
-
-    def needs_upgrade(self):
-        """Does this database need to be upgraded before writing to it?
-
-        If this function returns `True` then no functions that modify the
-        database (:meth:`index_file`,
-        :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
-        etc.) will work unless :meth:`upgrade` is called successfully first.
-
-        :returns: `True` or `False`
-        """
-        self._assert_db_is_initialized()
-        return self._needs_upgrade(self._db)
-
-    def upgrade(self):
-        """Upgrades the current database
-
-        After opening a database in read-write mode, the client should
-        check if an upgrade is needed (notmuch_database_needs_upgrade) and
-        if so, upgrade with this function before making any modifications.
-
-        NOT IMPLEMENTED: The optional progress_notify callback can be
-        used by the caller to provide progress indication to the
-        user. If non-NULL it will be called periodically with
-        'progress' as a floating-point value in the range of [0.0..1.0]
-        indicating the progress made so far in the upgrade process.
-
-        :TODO: catch exceptions, document return values and etc...
-        """
-        self._assert_db_is_initialized()
-        status = Database._upgrade(self._db, None, None)
-        # TODO: catch exceptions, document return values and etc
-        return status
-
-    _begin_atomic = nmlib.notmuch_database_begin_atomic
-    _begin_atomic.argtypes = [NotmuchDatabaseP]
-    _begin_atomic.restype = c_uint
-
-    def begin_atomic(self):
-        """Begin an atomic database operation
-
-        Any modifications performed between a successful
-        :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
-        the database atomically.  Note that, unlike a typical database
-        transaction, this only ensures atomicity, not durability;
-        neither begin nor end necessarily flush modifications to disk.
-
-        :returns: :attr:`STATUS`.SUCCESS or raises
-        :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
-                    Xapian exception occurred; atomic section not entered.
-
-        *Added in notmuch 0.9*"""
-        self._assert_db_is_initialized()
-        status = self._begin_atomic(self._db)
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        return status
-
-    _end_atomic = nmlib.notmuch_database_end_atomic
-    _end_atomic.argtypes = [NotmuchDatabaseP]
-    _end_atomic.restype = c_uint
-
-    def end_atomic(self):
-        """Indicate the end of an atomic database operation
-
-        See :meth:`begin_atomic` for details.
-
-        :returns: :attr:`STATUS`.SUCCESS or raises
-
-        :raises:
-            :exc:`NotmuchError`:
-                :attr:`STATUS`.XAPIAN_EXCEPTION
-                    A Xapian exception occurred; atomic section not
-                    ended.
-                :attr:`STATUS`.UNBALANCED_ATOMIC:
-                    end_atomic has been called more times than begin_atomic.
-
-        *Added in notmuch 0.9*"""
-        self._assert_db_is_initialized()
-        status = self._end_atomic(self._db)
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        return status
-
-    def get_directory(self, path):
-        """Returns a :class:`Directory` of path,
-
-        :param path: An unicode string containing the path relative to the path
-              of database (see :meth:`get_path`), or else should be an absolute
-              path with initial components that match the path of 'database'.
-        :returns: :class:`Directory` or raises an exception.
-        :raises: :exc:`FileError` if path is not relative database or absolute
-                 with initial components same as database.
-        """
-        self._assert_db_is_initialized()
-
-        # sanity checking if path is valid, and make path absolute
-        if path and path[0] == os.sep:
-            # we got an absolute path
-            if not path.startswith(self.get_path()):
-                # but its initial components are not equal to the db path
-                raise FileError('Database().get_directory() called '
-                                'with a wrong absolute path')
-            abs_dirpath = path
-        else:
-            #we got a relative path, make it absolute
-            abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
-
-        dir_p = NotmuchDirectoryP()
-        status = Database._get_directory(self._db, _str(path), byref(dir_p))
-
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        if not dir_p:
-            return None
-
-        # return the Directory, init it with the absolute path
-        return Directory(abs_dirpath, dir_p, self)
-
-    _get_default_indexopts = nmlib.notmuch_database_get_default_indexopts
-    _get_default_indexopts.argtypes = [NotmuchDatabaseP]
-    _get_default_indexopts.restype = NotmuchIndexoptsP
-
-    _indexopts_set_decrypt_policy = nmlib.notmuch_indexopts_set_decrypt_policy
-    _indexopts_set_decrypt_policy.argtypes = [NotmuchIndexoptsP, c_uint]
-    _indexopts_set_decrypt_policy.restype = None
-
-    _indexopts_destroy = nmlib.notmuch_indexopts_destroy
-    _indexopts_destroy.argtypes = [NotmuchIndexoptsP]
-    _indexopts_destroy.restype = None
-
-    _index_file = nmlib.notmuch_database_index_file
-    _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
-                             c_void_p,
-                             POINTER(NotmuchMessageP)]
-    _index_file.restype = c_uint
-
-    def index_file(self, filename, sync_maildir_flags=False, decrypt_policy=None):
-        """Adds a new message to the database
-
-        :param filename: should be a path relative to the path of the
-            open database (see :meth:`get_path`), or else should be an
-            absolute filename with initial components that match the
-            path of the 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.
-
-        :param sync_maildir_flags: If the message contains Maildir
-            flags, we will -depending on the notmuch configuration- sync
-            those tags to initial notmuch tags, if set to `True`. It is
-            `False` by default to remain consistent with the libnotmuch
-            API. You might want to look into the underlying method
-            :meth:`Message.maildir_flags_to_tags`.
-
-        :param decrypt_policy: If the message contains any encrypted
-            parts, and decrypt_policy is set to
-            :attr:`DECRYPTION_POLICY`.TRUE, notmuch will try to
-            decrypt the message and index the cleartext, stashing any
-            discovered session keys.  If it is set to
-            :attr:`DECRYPTION_POLICY`.FALSE, it will never try to
-            decrypt during indexing.  If it is set to
-            :attr:`DECRYPTION_POLICY`.AUTO, then it will try to use
-            any stashed session keys it knows about, but will not try
-            to access the user's secret keys.
-            :attr:`DECRYPTION_POLICY`.NOSTASH behaves the same as
-            :attr:`DECRYPTION_POLICY`.TRUE except that no session keys
-            are stashed in the database.  If decrypt_policy is set to
-            None (the default), then the database itself will decide
-            whether to decrypt, based on the `index.decrypt`
-            configuration setting (see notmuch-config(1)).
-
-        :returns: On success, we return
-
-           1) a :class:`Message` object that can be used for things
-              such as adding tags to the just-added message.
-           2) one of the following :attr:`STATUS` values:
-
-              :attr:`STATUS`.SUCCESS
-                  Message successfully added to database.
-              :attr:`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 list of the filenames for the existing message.
-
-        :rtype:   2-tuple(:class:`Message`, :attr:`STATUS`)
-
-        :raises: Raises a :exc:`NotmuchError` with the following meaning.
-              If such an exception occurs, nothing was added to the database.
-
-              :attr:`STATUS`.FILE_ERROR
-                      An error occurred trying to open the file, (such as
-                      permission denied, or file not found, etc.).
-              :attr:`STATUS`.FILE_NOT_EMAIL
-                      The contents of filename don't look like an email
-                      message.
-              :attr:`STATUS`.READ_ONLY_DATABASE
-                      Database was opened in read-only mode so no message can
-                      be added.
-        """
-        self._assert_db_is_initialized()
-        msg_p = NotmuchMessageP()
-        indexopts = c_void_p(None)
-        if decrypt_policy is not None:
-            indexopts = self._get_default_indexopts(self._db)
-            self._indexopts_set_decrypt_policy(indexopts, decrypt_policy)
-
-        status = self._index_file(self._db, _str(filename), indexopts, byref(msg_p))
-
-        if indexopts:
-            self._indexopts_destroy(indexopts)
-
-        if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
-            raise NotmuchError(status)
-
-        #construct Message() and return
-        msg = Message(msg_p, self)
-        #automatic sync initial tags from Maildir flags
-        if sync_maildir_flags:
-            msg.maildir_flags_to_tags()
-        return (msg, status)
-
-    def add_message(self, filename, sync_maildir_flags=False):
-        """Deprecated alias for :meth:`index_file`
-        """
-        warnings.warn(
-                "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
-
-        return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
-
-    _remove_message = nmlib.notmuch_database_remove_message
-    _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
-    _remove_message.restype = c_uint
-
-    def remove_message(self, filename):
-        """Removes a message (filename) 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.
-
-        :returns: A :attr:`STATUS` value with the following meaning:
-
-             :attr:`STATUS`.SUCCESS
-               The last filename was removed and the message was removed
-               from the database.
-             :attr:`STATUS`.DUPLICATE_MESSAGE_ID
-               This filename was removed but the message persists in the
-               database with at least one other filename.
-
-        :raises: Raises a :exc:`NotmuchError` with the following meaning.
-             If such an exception occurs, nothing was removed from the
-             database.
-
-             :attr:`STATUS`.READ_ONLY_DATABASE
-               Database was opened in read-only mode so no message can be
-               removed.
-        """
-        self._assert_db_is_initialized()
-        status = self._remove_message(self._db, _str(filename))
-        if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
-            raise NotmuchError(status)
-        return status
-
-    def find_message(self, msgid):
-        """Returns a :class:`Message` as identified by its message ID
-
-        Wraps the underlying *notmuch_database_find_message* function.
-
-        :param msgid: The message ID
-        :type msgid: unicode or str
-        :returns: :class:`Message` or `None` if no message is found.
-        :raises:
-            :exc:`OutOfMemoryError`
-                  If an Out-of-memory occurred while constructing the message.
-            :exc:`XapianError`
-                  In case of a Xapian Exception. These exceptions
-                  include "Database modified" situations, e.g. when the
-                  notmuch database has been modified by another program
-                  in the meantime. In this case, you should close and
-                  reopen the database and retry.
-            :exc:`NotInitializedError` if
-                    the database was not initialized.
-        """
-        self._assert_db_is_initialized()
-        msg_p = NotmuchMessageP()
-        status = Database._find_message(self._db, _str(msgid), byref(msg_p))
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        return msg_p and Message(msg_p, self) or None
-
-    def find_message_by_filename(self, filename):
-        """Find a message with the given filename
-
-        :returns: If the database contains a message with the given
-            filename, then a class:`Message:` is returned.  This
-            function returns None if no message is found with the given
-            filename.
-
-        :raises: :exc:`OutOfMemoryError` if an Out-of-memory occurred while
-                 constructing the message.
-        :raises: :exc:`XapianError` in case of a Xapian Exception.
-                 These exceptions include "Database modified"
-                 situations, e.g. when the notmuch database has been
-                 modified by another program in the meantime. In this
-                 case, you should close and reopen the database and
-                 retry.
-        :raises: :exc:`NotInitializedError` if the database was not
-                 initialized.
-
-        *Added in notmuch 0.9*"""
-        self._assert_db_is_initialized()
-
-        msg_p = NotmuchMessageP()
-        status = Database._find_message_by_filename(self._db, _str(filename),
-                                                    byref(msg_p))
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        return msg_p and Message(msg_p, self) or None
-
-    def get_all_tags(self):
-        """Returns :class:`Tags` with a list of all tags found in the database
-
-        :returns: :class:`Tags`
-        :exception: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
-                    on error
-        """
-        self._assert_db_is_initialized()
-        tags_p = Database._get_all_tags(self._db)
-        if not tags_p:
-            raise NullPointerError()
-        return Tags(tags_p, self)
-
-    def create_query(self, querystring):
-        """Returns a :class:`Query` derived from this database
-
-        This is a shorthand method for doing::
-
-          # short version
-          # Automatically frees the Database() when 'q' is deleted
-
-          q  = Database(dbpath).create_query('from:"Biene Maja"')
-
-          # long version, which is functionally equivalent but will keep the
-          # Database in the 'db' variable around after we delete 'q':
-
-          db = Database(dbpath)
-          q  = Query(db,'from:"Biene Maja"')
-
-        This function is a python extension and not in the underlying C API.
-        """
-        return Query(self, querystring)
-
-    """notmuch_database_status_string"""
-    _status_string = nmlib.notmuch_database_status_string
-    _status_string.argtypes = [NotmuchDatabaseP]
-    _status_string.restype = c_char_p
-
-    def status_string(self):
-        """Returns the status string of the database
-
-        This is sometimes used for additional error reporting
-        """
-        self._assert_db_is_initialized()
-        s = Database._status_string(self._db)
-        if s:
-            return s.decode('utf-8', 'ignore')
-        return s
-
-    def __repr__(self):
-        return "'Notmuch DB " + self.get_path() + "'"
-
-    def _get_user_default_db(self):
-        """ Reads a user's notmuch config and returns his db location
-
-        Throws a NotmuchError if it cannot find it"""
-        config = SafeConfigParser()
-        conf_f = os.getenv('NOTMUCH_CONFIG',
-                           os.path.expanduser('~/.notmuch-config'))
-        config.readfp(codecs.open(conf_f, 'r', 'utf-8'))
-        if not config.has_option('database', 'path'):
-            raise NotmuchError(message="No DB path specified"
-                                       " and no user default found")
-        db_path = config.get('database', 'path')
-        if not os.path.isabs(db_path):
-            db_path = os.path.expanduser(os.path.join("~", db_path))
-        return db_path
-
-    """notmuch_database_get_config"""
-    _get_config = nmlib.notmuch_database_get_config
-    _get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)]
-    _get_config.restype = c_uint
-
-    def get_config(self, key):
-        """Return the value of the given config key.
-
-        Note that only config values that are stored in the database are
-        searched and returned.  The config file is not read.
-
-        :param key: the config key under which a value should be looked up, it
-                    should probably be in the form "section.key"
-        :type key:  str
-        :returns:   the config value or the empty string if no value is present
-                    for that key
-        :rtype:     str
-        :raises:    :exc:`NotmuchError` in case of failure.
-
-        """
-        self._assert_db_is_initialized()
-        return_string = c_char_p()
-        status = self._get_config(self._db, _str(key), byref(return_string))
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        return return_string.value.decode('utf-8')
-
-    """notmuch_database_get_config_list"""
-    _get_config_list = nmlib.notmuch_database_get_config_list
-    _get_config_list.argtypes = [NotmuchDatabaseP, c_char_p,
-                                 POINTER(NotmuchConfigListP)]
-    _get_config_list.restype = c_uint
-
-    _config_list_valid = nmlib.notmuch_config_list_valid
-    _config_list_valid.argtypes = [NotmuchConfigListP]
-    _config_list_valid.restype = bool
-
-    _config_list_key = nmlib.notmuch_config_list_key
-    _config_list_key.argtypes = [NotmuchConfigListP]
-    _config_list_key.restype = c_char_p
-
-    _config_list_value = nmlib.notmuch_config_list_value
-    _config_list_value.argtypes = [NotmuchConfigListP]
-    _config_list_value.restype = c_char_p
-
-    _config_list_move_to_next = nmlib.notmuch_config_list_move_to_next
-    _config_list_move_to_next.argtypes = [NotmuchConfigListP]
-    _config_list_move_to_next.restype = None
-
-    _config_list_destroy = nmlib.notmuch_config_list_destroy
-    _config_list_destroy.argtypes = [NotmuchConfigListP]
-    _config_list_destroy.restype = None
-
-    def get_configs(self, prefix=''):
-        """Return a generator of key, value pairs where the start of key
-        matches the given prefix
-
-        Note that only config values that are stored in the database are
-        searched and returned.  The config file is not read.  If no `prefix` is
-        given all config values are returned.
-
-        This could be used to get all named queries into a dict for example::
-
-            queries = {k[6:]: v for k, v in db.get_configs('query.')}
-
-        :param prefix: a string by which the keys should be selected
-        :type prefix:  str
-        :yields:       all key-value pairs where `prefix` matches the beginning
-                       of the key
-        :ytype:        pairs of str
-        :raises:      :exc:`NotmuchError` in case of failure.
-
-        """
-        self._assert_db_is_initialized()
-        config_list_p = NotmuchConfigListP()
-        status = self._get_config_list(self._db, _str(prefix),
-                                       byref(config_list_p))
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-        while self._config_list_valid(config_list_p):
-            key = self._config_list_key(config_list_p).decode('utf-8')
-            value = self._config_list_value(config_list_p).decode('utf-8')
-            yield key, value
-            self._config_list_move_to_next(config_list_p)
-
-    """notmuch_database_set_config"""
-    _set_config = nmlib.notmuch_database_set_config
-    _set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p]
-    _set_config.restype = c_uint
-
-    def set_config(self, key, value):
-        """Set a config value in the notmuch database.
-
-        If an empty string is provided as `value` the `key` is unset!
-
-        :param key:   the key to set
-        :type key:    str
-        :param value: the value to store under `key`
-        :type value:  str
-        :returns:     None
-        :raises:      :exc:`NotmuchError` in case of failure.
-
-        """
-        self._assert_db_is_initialized()
-        status = self._set_config(self._db, _str(key), _str(value))
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py
deleted file mode 100644 (file)
index b30c9e3..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-from ctypes import c_uint, c_long
-from .globals import (
-    nmlib,
-    NotmuchDirectoryP,
-    NotmuchFilenamesP
-)
-from .errors import (
-    STATUS,
-    NotmuchError,
-    NotInitializedError,
-)
-from .filenames import Filenames
-
-class Directory(object):
-    """Represents a directory entry in the notmuch directory
-
-    Modifying attributes of this object will modify the
-    database, not the real directory attributes.
-
-    The Directory object is usually derived from another object
-    e.g. via :meth:`Database.get_directory`, and will automatically be
-    become invalid whenever that parent is deleted. You should
-    therefore initialized this object handing it a reference to the
-    parent, preventing the parent from automatically being garbage
-    collected.
-    """
-
-    """notmuch_directory_get_mtime"""
-    _get_mtime = nmlib.notmuch_directory_get_mtime
-    _get_mtime.argtypes = [NotmuchDirectoryP]
-    _get_mtime.restype = c_long
-
-    """notmuch_directory_set_mtime"""
-    _set_mtime = nmlib.notmuch_directory_set_mtime
-    _set_mtime.argtypes = [NotmuchDirectoryP, c_long]
-    _set_mtime.restype = c_uint
-
-    """notmuch_directory_get_child_files"""
-    _get_child_files = nmlib.notmuch_directory_get_child_files
-    _get_child_files.argtypes = [NotmuchDirectoryP]
-    _get_child_files.restype = NotmuchFilenamesP
-
-    """notmuch_directory_get_child_directories"""
-    _get_child_directories = nmlib.notmuch_directory_get_child_directories
-    _get_child_directories.argtypes = [NotmuchDirectoryP]
-    _get_child_directories.restype = NotmuchFilenamesP
-
-    def _assert_dir_is_initialized(self):
-        """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
-        if dir_p is None"""
-        if not self._dir_p:
-            raise NotInitializedError()
-
-    def __init__(self, path, dir_p, parent):
-        """
-        :param path:   The absolute path of the directory object.
-        :param dir_p:  The pointer to an internal notmuch_directory_t object.
-        :param parent: The object this Directory is derived from
-                       (usually a :class:`Database`). We do not directly use
-                       this, but store a reference to it as long as
-                       this Directory object lives. This keeps the
-                       parent object alive.
-        """
-        self._path = path
-        self._dir_p = dir_p
-        self._parent = parent
-
-    def set_mtime(self, mtime):
-        """Sets the mtime value of this directory in the database
-
-        The intention is for the caller to use the mtime to allow efficient
-        identification of new messages to be added to the database. The
-        recommended usage is as follows:
-
-        * Read the mtime of a directory from the filesystem
-
-        * Call :meth:`Database.index_file` for all mail files in
-          the directory
-
-        * Call notmuch_directory_set_mtime with the mtime read from the
-          filesystem.  Then, when wanting to check for updates to the
-          directory in the future, the client can call :meth:`get_mtime`
-          and know that it only needs to add files if the mtime of the
-          directory and files are newer than the stored timestamp.
-
-          .. note::
-
-                :meth:`get_mtime` function does not allow the caller to
-                distinguish a timestamp of 0 from a non-existent timestamp. So
-                don't store a timestamp of 0 unless you are comfortable with
-                that.
-
-        :param mtime: A (time_t) timestamp
-        :raises: :exc:`XapianError` a Xapian exception occurred, mtime
-                 not stored
-        :raises: :exc:`ReadOnlyDatabaseError` the database was opened
-                 in read-only mode so directory mtime cannot be modified
-        :raises: :exc:`NotInitializedError` the directory object has not
-                 been initialized
-        """
-        self._assert_dir_is_initialized()
-        status = Directory._set_mtime(self._dir_p, mtime)
-
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-
-    def get_mtime(self):
-        """Gets the mtime value of this directory in the database
-
-        Retrieves a previously stored mtime for this directory.
-
-        :param mtime: A (time_t) timestamp
-        :raises: :exc:`NotmuchError`:
-
-                        :attr:`STATUS`.NOT_INITIALIZED
-                          The directory has not been initialized
-        """
-        self._assert_dir_is_initialized()
-        return Directory._get_mtime(self._dir_p)
-
-    # Make mtime attribute a property of Directory()
-    mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
-                     and setting of the Directory *mtime* (read-write)
-
-                     See :meth:`get_mtime` and :meth:`set_mtime` for usage and
-                     possible exceptions.""")
-
-    def get_child_files(self):
-        """Gets a Filenames iterator listing all the filenames of
-        messages in the database within the given directory.
-
-        The returned filenames will be the basename-entries only (not
-        complete paths.
-        """
-        self._assert_dir_is_initialized()
-        files_p = Directory._get_child_files(self._dir_p)
-        return Filenames(files_p, self)
-
-    def get_child_directories(self):
-        """Gets a :class:`Filenames` iterator listing all the filenames of
-        sub-directories in the database within the given directory
-
-        The returned filenames will be the basename-entries only (not
-        complete paths.
-        """
-        self._assert_dir_is_initialized()
-        files_p = Directory._get_child_directories(self._dir_p)
-        return Filenames(files_p, self)
-
-    @property
-    def path(self):
-        """Returns the absolute path of this Directory (read-only)"""
-        return self._path
-
-    def __repr__(self):
-        """Object representation"""
-        return "<notmuch Directory object '%s'>" % self._path
-
-    _destroy = nmlib.notmuch_directory_destroy
-    _destroy.argtypes = [NotmuchDirectoryP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the Directory"""
-        if self._dir_p:
-            self._destroy(self._dir_p)
diff --git a/bindings/python/notmuch/errors.py b/bindings/python/notmuch/errors.py
deleted file mode 100644 (file)
index b7684ef..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-from ctypes import c_char_p, c_int
-
-from .globals import (
-    nmlib,
-    Enum,
-    Python3StringMixIn,
-)
-
-class Status(Enum):
-    """Enum with a string representation of a notmuch_status_t value."""
-    _status2str = nmlib.notmuch_status_to_string
-    _status2str.restype = c_char_p
-    _status2str.argtypes = [c_int]
-
-    def __init__(self, statuslist):
-        """It is initialized with a list of strings that are available as
-        Status().string1 - Status().stringn attributes.
-        """
-        super(Status, self).__init__(statuslist)
-
-    @classmethod
-    def status2str(self, status):
-        """Get a (unicode) string representation of a notmuch_status_t value."""
-        # define strings for custom error messages
-        if status == STATUS.NOT_INITIALIZED:
-            return "Operation on uninitialized object impossible."
-        return unicode(Status._status2str(status))
-
-STATUS = Status(['SUCCESS',
-  'OUT_OF_MEMORY',
-  'READ_ONLY_DATABASE',
-  'XAPIAN_EXCEPTION',
-  'FILE_ERROR',
-  'FILE_NOT_EMAIL',
-  'DUPLICATE_MESSAGE_ID',
-  'NULL_POINTER',
-  'TAG_TOO_LONG',
-  'UNBALANCED_FREEZE_THAW',
-  'UNBALANCED_ATOMIC',
-  'UNSUPPORTED_OPERATION',
-  'UPGRADE_REQUIRED',
-  'PATH_ERROR',
-  'NOT_INITIALIZED'])
-"""STATUS is a class, whose attributes provide constants that serve as return
-indicators for notmuch functions. Currently the following ones are defined. For
-possible return values and specific meaning for each method, see the method
-description.
-
-  * SUCCESS
-  * OUT_OF_MEMORY
-  * READ_ONLY_DATABASE
-  * XAPIAN_EXCEPTION
-  * FILE_ERROR
-  * FILE_NOT_EMAIL
-  * DUPLICATE_MESSAGE_ID
-  * NULL_POINTER
-  * TAG_TOO_LONG
-  * UNBALANCED_FREEZE_THAW
-  * UNBALANCED_ATOMIC
-  * UNSUPPORTED_OPERATION
-  * UPGRADE_REQUIRED
-  * PATH_ERROR
-  * NOT_INITIALIZED
-
-Invoke the class method `notmuch.STATUS.status2str` with a status value as
-argument to receive a human readable string"""
-STATUS.__name__ = 'STATUS'
-
-
-class NotmuchError(Exception, Python3StringMixIn):
-    """Is initiated with a (notmuch.STATUS[, message=None]). It will not
-    return an instance of the class NotmuchError, but a derived instance
-    of a more specific Error Message, e.g. OutOfMemoryError. Each status
-    but SUCCESS has a corresponding subclassed Exception."""
-
-    @classmethod
-    def get_exc_subclass(cls, status):
-        """Returns a fine grained Exception() type,
-        detailing the error status"""
-        subclasses = {
-            STATUS.OUT_OF_MEMORY: OutOfMemoryError,
-            STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
-            STATUS.XAPIAN_EXCEPTION: XapianError,
-            STATUS.FILE_ERROR: FileError,
-            STATUS.FILE_NOT_EMAIL: FileNotEmailError,
-            STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
-            STATUS.NULL_POINTER: NullPointerError,
-            STATUS.TAG_TOO_LONG: TagTooLongError,
-            STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
-            STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
-            STATUS.UNSUPPORTED_OPERATION: UnsupportedOperationError,
-            STATUS.UPGRADE_REQUIRED: UpgradeRequiredError,
-            STATUS.PATH_ERROR: PathError,
-            STATUS.NOT_INITIALIZED: NotInitializedError,
-        }
-        assert 0 < status <= len(subclasses)
-        return subclasses[status]
-
-    def __new__(cls, *args, **kwargs):
-        """Return a correct subclass of NotmuchError if needed
-
-        We return a NotmuchError instance if status is None (or 0) and a
-        subclass that inherits from NotmuchError depending on the
-        'status' parameter otherwise."""
-        # get 'status'. Passed in as arg or kwarg?
-        status = args[0] if len(args) else kwargs.get('status', None)
-        # no 'status' or cls is subclass already, return 'cls' instance
-        if not status or cls != NotmuchError:
-            return super(NotmuchError, cls).__new__(cls)
-        subclass = cls.get_exc_subclass(status)  # which class to use?
-        return subclass.__new__(subclass, *args, **kwargs)
-
-    def __init__(self, status=None, message=None):
-        self.status = status
-        self.message = message
-
-    def __unicode__(self):
-        if self.message is not None:
-            return self.message
-        elif self.status is not None:
-            return STATUS.status2str(self.status)
-        else:
-            return 'Unknown error'
-
-
-# List of Subclassed exceptions that correspond to STATUS values and are
-# subclasses of NotmuchError.
-class OutOfMemoryError(NotmuchError):
-    status = STATUS.OUT_OF_MEMORY
-
-
-class ReadOnlyDatabaseError(NotmuchError):
-    status = STATUS.READ_ONLY_DATABASE
-
-
-class XapianError(NotmuchError):
-    status = STATUS.XAPIAN_EXCEPTION
-
-
-class FileError(NotmuchError):
-    status = STATUS.FILE_ERROR
-
-
-class FileNotEmailError(NotmuchError):
-    status = STATUS.FILE_NOT_EMAIL
-
-
-class DuplicateMessageIdError(NotmuchError):
-    status = STATUS.DUPLICATE_MESSAGE_ID
-
-
-class NullPointerError(NotmuchError):
-    status = STATUS.NULL_POINTER
-
-
-class TagTooLongError(NotmuchError):
-    status = STATUS.TAG_TOO_LONG
-
-
-class UnbalancedFreezeThawError(NotmuchError):
-    status = STATUS.UNBALANCED_FREEZE_THAW
-
-
-class UnbalancedAtomicError(NotmuchError):
-    status = STATUS.UNBALANCED_ATOMIC
-
-
-class UnsupportedOperationError(NotmuchError):
-    status = STATUS.UNSUPPORTED_OPERATION
-
-
-class UpgradeRequiredError(NotmuchError):
-    status = STATUS.UPGRADE_REQUIRED
-
-
-class PathError(NotmuchError):
-    status = STATUS.PATH_ERROR
-
-
-class NotInitializedError(NotmuchError):
-    """Derived from NotmuchError, this occurs if the underlying data
-    structure (e.g. database is not initialized (yet) or an iterator has
-    been exhausted. You can test for NotmuchError with .status =
-    STATUS.NOT_INITIALIZED"""
-    status = STATUS.NOT_INITIALIZED
diff --git a/bindings/python/notmuch/filenames.py b/bindings/python/notmuch/filenames.py
deleted file mode 100644 (file)
index 3bbc22b..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-from ctypes import c_char_p
-from .globals import (
-    nmlib,
-    NotmuchFilenamesP,
-    Python3StringMixIn,
-)
-from .errors import (
-    NullPointerError,
-    NotInitializedError,
-)
-
-
-class Filenames(Python3StringMixIn):
-    """Represents a list of filenames as returned by notmuch
-
-    Objects of this class implement the iterator protocol.
-
-    .. note::
-
-        The underlying library only provides a one-time iterator (it
-        cannot reset the iterator to the start). Thus iterating over
-        the function will "exhaust" the list of tags, and a subsequent
-        iteration attempt will raise a
-        :exc:`NotInitializedError`. Also note, that any function that
-        uses iteration (nearly all) will also exhaust the tags. So
-        both::
-
-           for name in filenames: print name
-
-        as well as::
-
-           list_of_names = list(names)
-
-        and even a simple::
-
-           #str() iterates over all tags to construct a space separated list
-           print(str(filenames))
-
-        will "exhaust" the Filenames. However, you can use
-        :meth:`Message.get_filenames` repeatedly to get fresh
-        Filenames objects to perform various actions on filenames.
-    """
-
-    #notmuch_filenames_get
-    _get = nmlib.notmuch_filenames_get
-    _get.argtypes = [NotmuchFilenamesP]
-    _get.restype = c_char_p
-
-    def __init__(self, files_p, parent):
-        """
-        :param files_p: A pointer to an underlying *notmuch_tags_t*
-             structure. These are not publicly exposed, so a user
-             will almost never instantiate a :class:`Tags` object
-             herself. They are usually handed back as a result,
-             e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
-             valid, we will raise an :exc:`NullPointerError`
-             if it is `None`.
-        :type files_p: :class:`ctypes.c_void_p`
-        :param parent: The parent object (ie :class:`Message` these
-             filenames are derived from, and saves a
-             reference to it, so we can automatically delete the db object
-             once all derived objects are dead.
-        """
-        if not files_p:
-            raise NullPointerError()
-
-        self._files_p = files_p
-        #save reference to parent object so we keep it alive
-        self._parent = parent
-
-    def __iter__(self):
-        """ Make Filenames an iterator """
-        return self
-
-    _valid = nmlib.notmuch_filenames_valid
-    _valid.argtypes = [NotmuchFilenamesP]
-    _valid.restype = bool
-
-    _move_to_next = nmlib.notmuch_filenames_move_to_next
-    _move_to_next.argtypes = [NotmuchFilenamesP]
-    _move_to_next.restype = None
-
-    def __next__(self):
-        if not self._files_p:
-            raise NotInitializedError()
-
-        if not self._valid(self._files_p):
-            self._files_p = None
-            raise StopIteration
-
-        file_ = Filenames._get(self._files_p)
-        self._move_to_next(self._files_p)
-        return file_.decode('utf-8', 'ignore')
-    next = __next__ # python2.x iterator protocol compatibility
-
-    def __unicode__(self):
-        """Represent Filenames() as newline-separated list of full paths
-
-        .. note::
-
-            This method exhausts the iterator object, so you will not be able to
-            iterate over them again.
-        """
-        return "\n".join(self)
-
-    _destroy = nmlib.notmuch_filenames_destroy
-    _destroy.argtypes = [NotmuchFilenamesP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch filenames"""
-        if self._files_p:
-            self._destroy(self._files_p)
diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py
deleted file mode 100644 (file)
index 11e328b..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-from ctypes import CDLL, Structure, POINTER
-from notmuch.version import SOVERSION
-
-#-----------------------------------------------------------------------------
-#package-global instance of the notmuch library
-try:
-    from os import uname
-    if uname()[0] == 'Darwin':
-        nmlib = CDLL("libnotmuch.{0:s}.dylib".format(SOVERSION))
-    else:
-        nmlib = CDLL("libnotmuch.so.{0:s}".format(SOVERSION))
-except:
-    raise ImportError("Could not find shared 'notmuch' library.")
-
-from .compat import Python3StringMixIn, encode_utf8 as _str
-
-# We import these on behalf of other modules.  Silence warning about
-# these symbols not being used.
-Python3StringMixIn
-_str
-
-class Enum(object):
-    """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc..."""
-    def __init__(self, names):
-        for number, name in enumerate(names):
-            setattr(self, name, number)
-
-
-class NotmuchDatabaseS(Structure):
-    pass
-NotmuchDatabaseP = POINTER(NotmuchDatabaseS)
-
-
-class NotmuchQueryS(Structure):
-    pass
-NotmuchQueryP = POINTER(NotmuchQueryS)
-
-
-class NotmuchThreadsS(Structure):
-    pass
-NotmuchThreadsP = POINTER(NotmuchThreadsS)
-
-
-class NotmuchThreadS(Structure):
-    pass
-NotmuchThreadP = POINTER(NotmuchThreadS)
-
-
-class NotmuchMessagesS(Structure):
-    pass
-NotmuchMessagesP = POINTER(NotmuchMessagesS)
-
-
-class NotmuchMessageS(Structure):
-    pass
-NotmuchMessageP = POINTER(NotmuchMessageS)
-
-
-class NotmuchMessagePropertiesS(Structure):
-    pass
-NotmuchMessagePropertiesP = POINTER(NotmuchMessagePropertiesS)
-
-
-class NotmuchTagsS(Structure):
-    pass
-NotmuchTagsP = POINTER(NotmuchTagsS)
-
-
-class NotmuchDirectoryS(Structure):
-    pass
-NotmuchDirectoryP = POINTER(NotmuchDirectoryS)
-
-
-class NotmuchFilenamesS(Structure):
-    pass
-NotmuchFilenamesP = POINTER(NotmuchFilenamesS)
-
-
-class NotmuchConfigListS(Structure):
-    pass
-NotmuchConfigListP = POINTER(NotmuchConfigListS)
-
-
-class NotmuchIndexoptsS(Structure):
-    pass
-NotmuchIndexoptsP = POINTER(NotmuchIndexoptsS)
diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py
deleted file mode 100644 (file)
index e71dbe3..0000000
+++ /dev/null
@@ -1,721 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-               Jesse Rosenthal <jrosenthal@jhu.edu>
-"""
-
-
-from ctypes import c_char_p, c_long, c_uint, c_int, POINTER, byref
-from datetime import date
-from .globals import (
-    nmlib,
-    Enum,
-    _str,
-    Python3StringMixIn,
-    NotmuchTagsP,
-    NotmuchMessageP,
-    NotmuchMessagesP,
-    NotmuchMessagePropertiesP,
-    NotmuchFilenamesP,
-)
-from .errors import (
-    STATUS,
-    NotmuchError,
-    NullPointerError,
-    NotInitializedError,
-)
-from .tag import Tags
-from .filenames import Filenames
-
-import email
-import sys
-
-
-class Message(Python3StringMixIn):
-    r"""Represents a single Email message
-
-    Technically, this wraps the underlying *notmuch_message_t*
-    structure. A user will usually not create these objects themselves
-    but get them as search results.
-
-    As it implements :meth:`__cmp__`, it is possible to compare two
-    :class:`Message`\s using `if msg1 == msg2: ...`.
-    """
-
-    """notmuch_message_get_filename (notmuch_message_t *message)"""
-    _get_filename = nmlib.notmuch_message_get_filename
-    _get_filename.argtypes = [NotmuchMessageP]
-    _get_filename.restype = c_char_p
-
-    """return all filenames for a message"""
-    _get_filenames = nmlib.notmuch_message_get_filenames
-    _get_filenames.argtypes = [NotmuchMessageP]
-    _get_filenames.restype = NotmuchFilenamesP
-
-    """notmuch_message_get_flag"""
-    _get_flag = nmlib.notmuch_message_get_flag
-    _get_flag.argtypes = [NotmuchMessageP, c_uint]
-    _get_flag.restype = bool
-
-    """notmuch_message_set_flag"""
-    _set_flag = nmlib.notmuch_message_set_flag
-    _set_flag.argtypes = [NotmuchMessageP, c_uint, c_int]
-    _set_flag.restype = None
-
-    """notmuch_message_get_message_id (notmuch_message_t *message)"""
-    _get_message_id = nmlib.notmuch_message_get_message_id
-    _get_message_id.argtypes = [NotmuchMessageP]
-    _get_message_id.restype = c_char_p
-
-    """notmuch_message_get_thread_id"""
-    _get_thread_id = nmlib.notmuch_message_get_thread_id
-    _get_thread_id.argtypes = [NotmuchMessageP]
-    _get_thread_id.restype = c_char_p
-
-    """notmuch_message_get_replies"""
-    _get_replies = nmlib.notmuch_message_get_replies
-    _get_replies.argtypes = [NotmuchMessageP]
-    _get_replies.restype = NotmuchMessagesP
-
-    """notmuch_message_get_tags (notmuch_message_t *message)"""
-    _get_tags = nmlib.notmuch_message_get_tags
-    _get_tags.argtypes = [NotmuchMessageP]
-    _get_tags.restype = NotmuchTagsP
-
-    _get_date = nmlib.notmuch_message_get_date
-    _get_date.argtypes = [NotmuchMessageP]
-    _get_date.restype = c_long
-
-    _get_header = nmlib.notmuch_message_get_header
-    _get_header.argtypes = [NotmuchMessageP, c_char_p]
-    _get_header.restype = c_char_p
-
-    """notmuch_status_t ..._maildir_flags_to_tags (notmuch_message_t *)"""
-    _tags_to_maildir_flags = nmlib.notmuch_message_tags_to_maildir_flags
-    _tags_to_maildir_flags.argtypes = [NotmuchMessageP]
-    _tags_to_maildir_flags.restype = c_int
-
-    """notmuch_status_t ..._tags_to_maildir_flags (notmuch_message_t *)"""
-    _maildir_flags_to_tags = nmlib.notmuch_message_maildir_flags_to_tags
-    _maildir_flags_to_tags.argtypes = [NotmuchMessageP]
-    _maildir_flags_to_tags.restype = c_int
-
-    """notmuch_message_get_property"""
-    _get_property = nmlib.notmuch_message_get_property
-    _get_property.argtypes = [NotmuchMessageP, c_char_p, POINTER(c_char_p)]
-    _get_property.restype = c_int
-
-    """notmuch_message_get_properties"""
-    _get_properties = nmlib.notmuch_message_get_properties
-    _get_properties.argtypes = [NotmuchMessageP, c_char_p, c_int]
-    _get_properties.restype = NotmuchMessagePropertiesP
-
-    """notmuch_message_properties_valid"""
-    _properties_valid = nmlib.notmuch_message_properties_valid
-    _properties_valid.argtypes = [NotmuchMessagePropertiesP]
-    _properties_valid.restype = bool
-
-    """notmuch_message_properties_value"""
-    _properties_value = nmlib.notmuch_message_properties_value
-    _properties_value.argtypes = [NotmuchMessagePropertiesP]
-    _properties_value.restype = c_char_p
-
-    """notmuch_message_properties_key"""
-    _properties_key = nmlib.notmuch_message_properties_key
-    _properties_key.argtypes = [NotmuchMessagePropertiesP]
-    _properties_key.restype = c_char_p
-
-    """notmuch_message_properties_move_to_next"""
-    _properties_move_to_next = nmlib.notmuch_message_properties_move_to_next
-    _properties_move_to_next.argtypes = [NotmuchMessagePropertiesP]
-    _properties_move_to_next.restype = None
-
-    #Constants: Flags that can be set/get with set_flag
-    FLAG = Enum(['MATCH'])
-
-    def __init__(self, msg_p, parent=None):
-        """
-        :param msg_p: A pointer to an internal notmuch_message_t
-            Structure.  If it is `None`, we will raise an
-            :exc:`NullPointerError`.
-
-        :param parent: A 'parent' object is passed which this message is
-              derived from. We save a reference to it, so we can
-              automatically delete the parent object once all derived
-              objects are dead.
-        """
-        if not msg_p:
-            raise NullPointerError()
-        self._msg = msg_p
-        #keep reference to parent, so we keep it alive
-        self._parent = parent
-
-    def get_message_id(self):
-        """Returns the message ID
-
-        :returns: String with a message ID
-        :raises: :exc:`NotInitializedError` if the message
-                    is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-        return Message._get_message_id(self._msg).decode('utf-8', 'ignore')
-
-    def get_thread_id(self):
-        """Returns the thread ID
-
-        The returned string belongs to 'message' will only be valid for as
-        long as the message is valid.
-
-        This function will not return `None` since Notmuch ensures that every
-        message belongs to a single thread.
-
-        :returns: String with a thread ID
-        :raises: :exc:`NotInitializedError` if the message
-                    is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        return Message._get_thread_id(self._msg).decode('utf-8', 'ignore')
-
-    def get_replies(self):
-        """Gets all direct replies to this message as :class:`Messages`
-        iterator
-
-        .. note::
-
-            This call only makes sense if 'message' was ultimately obtained from
-            a :class:`Thread` object, (such as by coming directly from the
-            result of calling :meth:`Thread.get_toplevel_messages` or by any
-            number of subsequent calls to :meth:`get_replies`). If this message
-            was obtained through some non-thread means, (such as by a call to
-            :meth:`Query.search_messages`), then this function will return
-            an empty Messages iterator.
-
-        :returns: :class:`Messages`.
-        :raises: :exc:`NotInitializedError` if the message
-                    is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        msgs_p = Message._get_replies(self._msg)
-
-        from .messages import Messages, EmptyMessagesResult
-
-        if not msgs_p:
-            return EmptyMessagesResult(self)
-
-        return Messages(msgs_p, self)
-
-    def get_date(self):
-        """Returns time_t of the message date
-
-        For the original textual representation of the Date header from the
-        message call notmuch_message_get_header() with a header value of
-        "date".
-
-        :returns: A time_t timestamp.
-        :rtype: c_unit64
-        :raises: :exc:`NotInitializedError` if the message
-                    is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-        return Message._get_date(self._msg)
-
-    def get_header(self, header):
-        """Get the value of the specified header.
-
-        The value will be read from the actual message file, not from
-        the notmuch database. The header name is case insensitive.
-
-        Returns an empty string ("") if the message does not contain a
-        header line matching 'header'.
-
-        :param header: The name of the header to be retrieved.
-                       It is not case-sensitive.
-        :type header: str
-        :returns: The header value as string
-        :raises: :exc:`NotInitializedError` if the message is not
-                 initialized
-        :raises: :exc:`NullPointerError` if any error occurred
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        #Returns NULL if any error occurs.
-        header = Message._get_header(self._msg, _str(header))
-        if header == None:
-            raise NullPointerError()
-        return header.decode('UTF-8', 'ignore')
-
-    def get_filename(self):
-        """Returns the file path of the message file
-
-        :returns: Absolute file path & name of the message file
-        :raises: :exc:`NotInitializedError` if the message
-              is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-        return Message._get_filename(self._msg).decode('utf-8', 'ignore')
-
-    def get_filenames(self):
-        """Get all filenames for the email corresponding to 'message'
-
-        Returns a Filenames() generator with all absolute filepaths for
-        messages recorded to have the same Message-ID. These files must
-        not necessarily have identical content."""
-        if not self._msg:
-            raise NotInitializedError()
-
-        files_p = Message._get_filenames(self._msg)
-
-        return Filenames(files_p, self)
-
-    def get_flag(self, flag):
-        """Checks whether a specific flag is set for this message
-
-        The method :meth:`Query.search_threads` sets
-        *Message.FLAG.MATCH* for those messages that match the
-        query. This method allows us to get the value of this flag.
-
-        :param flag: One of the :attr:`Message.FLAG` values (currently only
-                     *Message.FLAG.MATCH*
-        :returns: An unsigned int (0/1), indicating whether the flag is set.
-        :raises: :exc:`NotInitializedError` if the message
-              is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-        return Message._get_flag(self._msg, flag)
-
-    def set_flag(self, flag, value):
-        """Sets/Unsets a specific flag for this message
-
-        :param flag: One of the :attr:`Message.FLAG` values (currently only
-                     *Message.FLAG.MATCH*
-        :param value: A bool indicating whether to set or unset the flag.
-
-        :raises: :exc:`NotInitializedError` if the message
-              is not initialized.
-        """
-        if not self._msg:
-            raise NotInitializedError()
-        self._set_flag(self._msg, flag, value)
-
-    def get_tags(self):
-        """Returns the message tags
-
-        :returns: A :class:`Tags` iterator.
-        :raises: :exc:`NotInitializedError` if the message is not
-                 initialized
-        :raises: :exc:`NullPointerError` if any error occurred
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        tags_p = Message._get_tags(self._msg)
-        if not tags_p:
-            raise NullPointerError()
-        return Tags(tags_p, self)
-
-    _add_tag = nmlib.notmuch_message_add_tag
-    _add_tag.argtypes = [NotmuchMessageP, c_char_p]
-    _add_tag.restype = c_uint
-
-    def add_tag(self, tag, sync_maildir_flags=False):
-        """Adds a tag to the given message
-
-        Adds a tag to the current message. The maximal tag length is defined in
-        the notmuch library and is currently 200 bytes.
-
-        :param tag: String with a 'tag' to be added.
-
-        :param sync_maildir_flags: If notmuch configuration is set to do
-            this, add maildir flags corresponding to notmuch tags. See
-            underlying method :meth:`tags_to_maildir_flags`. Use False
-            if you want to add/remove many tags on a message without
-            having to physically rename the file every time. Do note,
-            that this will do nothing when a message is frozen, as tag
-            changes will not be committed to the database yet.
-
-        :returns: STATUS.SUCCESS if the tag was successfully added.
-                  Raises an exception otherwise.
-        :raises: :exc:`NullPointerError` if the `tag` argument is NULL
-        :raises: :exc:`TagTooLongError` if the length of `tag` exceeds
-                 Message.NOTMUCH_TAG_MAX)
-        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
-                 in read-only mode so message cannot be modified
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        status = self._add_tag(self._msg, _str(tag))
-
-        # bail out on failure
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-
-        if sync_maildir_flags:
-            self.tags_to_maildir_flags()
-        return STATUS.SUCCESS
-
-    _remove_tag = nmlib.notmuch_message_remove_tag
-    _remove_tag.argtypes = [NotmuchMessageP, c_char_p]
-    _remove_tag.restype = c_uint
-
-    def remove_tag(self, tag, sync_maildir_flags=False):
-        """Removes a tag from the given message
-
-        If the message has no such tag, this is a non-operation and
-        will report success anyway.
-
-        :param tag: String with a 'tag' to be removed.
-        :param sync_maildir_flags: If notmuch configuration is set to do
-            this, add maildir flags corresponding to notmuch tags. See
-            underlying method :meth:`tags_to_maildir_flags`. Use False
-            if you want to add/remove many tags on a message without
-            having to physically rename the file every time. Do note,
-            that this will do nothing when a message is frozen, as tag
-            changes will not be committed to the database yet.
-
-        :returns: STATUS.SUCCESS if the tag was successfully removed or if
-                  the message had no such tag.
-                  Raises an exception otherwise.
-        :raises: :exc:`NullPointerError` if the `tag` argument is NULL
-        :raises: :exc:`TagTooLongError` if the length of `tag` exceeds
-                 Message.NOTMUCH_TAG_MAX)
-        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
-                 in read-only mode so message cannot be modified
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        status = self._remove_tag(self._msg, _str(tag))
-        # bail out on error
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-
-        if sync_maildir_flags:
-            self.tags_to_maildir_flags()
-        return STATUS.SUCCESS
-
-    _remove_all_tags = nmlib.notmuch_message_remove_all_tags
-    _remove_all_tags.argtypes = [NotmuchMessageP]
-    _remove_all_tags.restype = c_uint
-
-    def remove_all_tags(self, sync_maildir_flags=False):
-        """Removes all tags from the given message.
-
-        See :meth:`freeze` for an example showing how to safely
-        replace tag values.
-
-
-        :param sync_maildir_flags: If notmuch configuration is set to do
-            this, add maildir flags corresponding to notmuch tags. See
-            :meth:`tags_to_maildir_flags`. Use False if you want to
-            add/remove many tags on a message without having to
-            physically rename the file every time. Do note, that this
-            will do nothing when a message is frozen, as tag changes
-            will not be committed to the database yet.
-
-        :returns: STATUS.SUCCESS if the tags were successfully removed.
-                  Raises an exception otherwise.
-        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
-                 in read-only mode so message cannot be modified
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        status = self._remove_all_tags(self._msg)
-
-        # bail out on error
-        if status != STATUS.SUCCESS:
-            raise NotmuchError(status)
-
-        if sync_maildir_flags:
-            self.tags_to_maildir_flags()
-        return STATUS.SUCCESS
-
-    _freeze = nmlib.notmuch_message_freeze
-    _freeze.argtypes = [NotmuchMessageP]
-    _freeze.restype = c_uint
-
-    def get_property(self, prop):
-        """ Retrieve the value for a single property key
-
-        :param prop: The name of the property to get.
-        :returns: String with the property value or None if there is no such
-                  key. In the case of multiple values for the given key, the
-                  first one is retrieved.
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        value = c_char_p()
-        status = Message._get_property(self._msg, _str(prop), byref(value))
-        if status != 0:
-            raise NotmuchError(status)
-
-        if value is None or value.value is None:
-            return None
-        return value.value.decode('utf-8')
-
-    def get_properties(self, prop="", exact=False):
-        """ Get the properties of the message, returning a generator of
-        name, value pairs.
-
-        The generator will yield once per value. There might be more than one
-        value on each name, so the generator might yield the same name several
-        times.
-
-        :param prop: The name of the property to get. Otherwise it will return
-                     the full list of properties of the message.
-        :param exact: if True, require exact match with key. Otherwise
-                      treat as prefix.
-        :yields:  Each property values as a pair of `name, value`
-        :ytype:   pairs of str
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        properties = Message._get_properties(self._msg, _str(prop), exact)
-        while Message._properties_valid(properties):
-            key = Message._properties_key(properties)
-            value = Message._properties_value(properties)
-            yield key.decode("utf-8"), value.decode("utf-8")
-            Message._properties_move_to_next(properties)
-
-    def freeze(self):
-        """Freezes the current state of 'message' within the database
-
-        This means that changes to the message state, (via :meth:`add_tag`,
-        :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
-        committed to the database until the message is :meth:`thaw` ed.
-
-        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::
-
-          msg.freeze()
-          msg.remove_all_tags(False)
-          for tag in new_tags:
-              msg.add_tag(tag, False)
-          msg.thaw()
-          msg.tags_to_maildir_flags()
-
-        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
-        :meth:`remove_all_tags` but before :meth:`add_tag`.
-
-        :returns: STATUS.SUCCESS if the message was successfully frozen.
-                  Raises an exception otherwise.
-        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
-                 in read-only mode so message cannot be modified
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        status = self._freeze(self._msg)
-
-        if STATUS.SUCCESS == status:
-            # return on success
-            return status
-
-        raise NotmuchError(status)
-
-    _thaw = nmlib.notmuch_message_thaw
-    _thaw.argtypes = [NotmuchMessageP]
-    _thaw.restype = c_uint
-
-    def thaw(self):
-        """Thaws the current 'message'
-
-        Thaw the current 'message', synchronizing any changes that may have
-        occurred while 'message' was frozen into the notmuch database.
-
-        See :meth:`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.
-
-        :returns: STATUS.SUCCESS if the message was successfully frozen.
-                  Raises an exception otherwise.
-        :raises: :exc:`UnbalancedFreezeThawError` if an attempt was made
-                 to thaw an unfrozen message. That is, there have been
-                 an unbalanced number of calls to :meth:`freeze` and
-                 :meth:`thaw`.
-        :raises: :exc:`NotInitializedError` if message has not been
-                 initialized
-        """
-        if not self._msg:
-            raise NotInitializedError()
-
-        status = self._thaw(self._msg)
-
-        if STATUS.SUCCESS == status:
-            # return on success
-            return status
-
-        raise NotmuchError(status)
-
-    def is_match(self):
-        """(Not implemented)"""
-        return self.get_flag(Message.FLAG.MATCH)
-
-    def tags_to_maildir_flags(self):
-        """Synchronize notmuch tags to file Maildir flags
-
-              'D' if the message has the "draft" tag
-              'F' if the message has the "flagged" tag
-              'P' if the message has the "passed" tag
-              'R' if the message has the "replied" tag
-              'S' if the message does not have the "unread" tag
-
-        Any existing flags unmentioned in the list above will be
-        preserved in the renaming.
-
-        Also, if this filename is in a directory named "new", rename it
-        to be within the neighboring directory named "cur".
-
-        Do note that calling this method while a message is frozen might
-        not work yet, as the modified tags have not been committed yet
-        to the database.
-
-        :returns: a :class:`STATUS` value. In short, you want to see
-            notmuch.STATUS.SUCCESS here. See there for details."""
-        if not self._msg:
-            raise NotInitializedError()
-        return Message._tags_to_maildir_flags(self._msg)
-
-    def maildir_flags_to_tags(self):
-        """Synchronize file Maildir flags to notmuch tags
-
-            Flag    Action if present
-            ----    -----------------
-            'D'     Adds the "draft" tag to the message
-            'F'     Adds the "flagged" tag to the message
-            'P'     Adds the "passed" tag to the message
-            'R'     Adds the "replied" tag to the message
-            'S'     Removes the "unread" tag from the message
-
-        For each flag that is not present, the opposite action
-        (add/remove) is performed for the corresponding tags.  If there
-        are multiple filenames associated with this message, the flag is
-        considered present if it appears in one or more filenames. (That
-        is, the flags from the multiple filenames are combined with the
-        logical OR operator.)
-
-        As a convenience, you can set the sync_maildir_flags parameter in
-        :meth:`Database.index_file` to implicitly call this.
-
-        :returns: a :class:`STATUS`. In short, you want to see
-            notmuch.STATUS.SUCCESS here. See there for details."""
-        if not self._msg:
-            raise NotInitializedError()
-        return Message._maildir_flags_to_tags(self._msg)
-
-    def __repr__(self):
-        """Represent a Message() object by str()"""
-        return self.__str__()
-
-    def __unicode__(self):
-        format = "%s (%s) (%s)"
-        return format % (self.get_header('from'),
-                         self.get_tags(),
-                         date.fromtimestamp(self.get_date()),
-                        )
-
-    def get_message_parts(self):
-        """Output like notmuch show"""
-        fp = open(self.get_filename(), 'rb')
-        if sys.version_info[0] < 3:
-            email_msg = email.message_from_file(fp)
-        else:
-            email_msg = email.message_from_binary_file(fp)
-        fp.close()
-
-        out = []
-        for msg in email_msg.walk():
-            if not msg.is_multipart():
-                out.append(msg)
-        return out
-
-    def get_part(self, num):
-        """Returns the nth message body part"""
-        parts = self.get_message_parts()
-        if (num <= 0 or num > len(parts)):
-            return ""
-        else:
-            out_part = parts[(num - 1)]
-            return out_part.get_payload(decode=True)
-
-    def __hash__(self):
-        """Implement hash(), so we can use Message() sets"""
-        file = self.get_filename()
-        if not file:
-            return None
-        return hash(file)
-
-    def __cmp__(self, other):
-        """Implement cmp(), so we can compare Message()s
-
-        2 messages are considered equal if they point to the same
-        Message-Id and if they point to the same file names. If 2
-        Messages derive from different queries where some files have
-        been added or removed, the same messages would not be considered
-        equal (as they do not point to the same set of files
-        any more)."""
-        res = cmp(self.get_message_id(), other.get_message_id())
-        if res:
-            res = cmp(list(self.get_filenames()), list(other.get_filenames()))
-        return res
-
-    _destroy = nmlib.notmuch_message_destroy
-    _destroy.argtypes = [NotmuchMessageP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch Message"""
-        if self._msg:
-            self._destroy(self._msg)
diff --git a/bindings/python/notmuch/messages.py b/bindings/python/notmuch/messages.py
deleted file mode 100644 (file)
index 3801c66..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-               Jesse Rosenthal <jrosenthal@jhu.edu>
-"""
-
-from .globals import (
-    nmlib,
-    NotmuchTagsP,
-    NotmuchMessageP,
-    NotmuchMessagesP,
-)
-from .errors import (
-    NullPointerError,
-    NotInitializedError,
-)
-from .tag import Tags
-from .message import Message
-
-class Messages(object):
-    r"""Represents a list of notmuch messages
-
-    This object provides an iterator over a list of notmuch messages
-    (Technically, it provides a wrapper for the underlying
-    *notmuch_messages_t* structure). Do note that the underlying library
-    only provides a one-time iterator (it cannot reset the iterator to
-    the start). Thus iterating over the function will "exhaust" the list
-    of messages, and a subsequent iteration attempt will raise a
-    :exc:`NotInitializedError`. If you need to
-    re-iterate over a list of messages you will need to retrieve a new
-    :class:`Messages` object or cache your :class:`Message`\s in a list
-    via::
-
-       msglist = list(msgs)
-
-    You can store and reuse the single :class:`Message` objects as often
-    as you want as long as you keep the parent :class:`Messages` object
-    around. (Due to hierarchical memory allocation, all derived
-    :class:`Message` objects will be invalid when we delete the parent
-    :class:`Messages` object, even if it was already exhausted.) So
-    this works::
-
-      db   = Database()
-      msgs = Query(db,'').search_messages() #get a Messages() object
-      msglist = list(msgs)
-
-      # msgs is "exhausted" now and msgs.next() will raise an exception.
-      # However it will be kept alive until all retrieved Message()
-      # objects are also deleted. If you do e.g. an explicit del(msgs)
-      # here, the following lines would fail.
-
-      # You can reiterate over *msglist* however as often as you want.
-      # It is simply a list with :class:`Message`s.
-
-      print (msglist[0].get_filename())
-      print (msglist[1].get_filename())
-      print (msglist[0].get_message_id())
-
-
-    As :class:`Message` implements both __hash__() and __cmp__(), it is
-    possible to make sets out of :class:`Messages` and use set
-    arithmetic (this happens in python and will of course be *much*
-    slower than redoing a proper query with the appropriate filters::
-
-        s1, s2 = set(msgs1), set(msgs2)
-        s.union(s2)
-        s1 -= s2
-        ...
-
-    Be careful when using set arithmetic between message sets derived
-    from different Databases (ie the same database reopened after
-    messages have changed). If messages have added or removed associated
-    files in the meantime, it is possible that the same message would be
-    considered as a different object (as it points to a different file).
-    """
-
-    #notmuch_messages_get
-    _get = nmlib.notmuch_messages_get
-    _get.argtypes = [NotmuchMessagesP]
-    _get.restype = NotmuchMessageP
-
-    _collect_tags = nmlib.notmuch_messages_collect_tags
-    _collect_tags.argtypes = [NotmuchMessagesP]
-    _collect_tags.restype = NotmuchTagsP
-
-    def __init__(self, msgs_p, parent=None):
-        """
-        :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
-             structure. These are not publicly exposed, so a user
-             will almost never instantiate a :class:`Messages` object
-             herself. They are usually handed back as a result,
-             e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
-             valid, we will raise an :exc:`NullPointerError` if it is
-             `None`.
-        :type msgs_p: :class:`ctypes.c_void_p`
-        :param parent: The parent object
-             (ie :class:`Query`) these tags are derived from. It saves
-             a reference to it, so we can automatically delete the db
-             object once all derived objects are dead.
-        :TODO: Make the iterator work more than once and cache the tags in
-               the Python object.(?)
-        """
-        if not msgs_p:
-            raise NullPointerError()
-
-        self._msgs = msgs_p
-        #store parent, so we keep them alive as long as self  is alive
-        self._parent = parent
-
-    def collect_tags(self):
-        """Return the unique :class:`Tags` in the contained messages
-
-        :returns: :class:`Tags`
-        :exceptions: :exc:`NotInitializedError` if not init'ed
-
-        .. note::
-
-            :meth:`collect_tags` will iterate over the messages and therefore
-            will not allow further iterations.
-        """
-        if not self._msgs:
-            raise NotInitializedError()
-
-        # collect all tags (returns NULL on error)
-        tags_p = Messages._collect_tags(self._msgs)
-        #reset _msgs as we iterated over it and can do so only once
-        self._msgs = None
-
-        if not tags_p:
-            raise NullPointerError()
-        return Tags(tags_p, self)
-
-    def __iter__(self):
-        """ Make Messages an iterator """
-        return self
-
-    _valid = nmlib.notmuch_messages_valid
-    _valid.argtypes = [NotmuchMessagesP]
-    _valid.restype = bool
-
-    _move_to_next = nmlib.notmuch_messages_move_to_next
-    _move_to_next.argtypes = [NotmuchMessagesP]
-    _move_to_next.restype = None
-
-    def __next__(self):
-        if not self._msgs:
-            raise NotInitializedError()
-
-        if not self._valid(self._msgs):
-            self._msgs = None
-            raise StopIteration
-
-        msg = Message(Messages._get(self._msgs), self)
-        self._move_to_next(self._msgs)
-        return msg
-    next = __next__ # python2.x iterator protocol compatibility
-
-    def __nonzero__(self):
-        '''
-        Implement truth value testing. If __nonzero__ is not
-        implemented, the python runtime would fall back to `len(..) >
-        0` thus exhausting the iterator.
-
-        :returns: True if the wrapped iterator has at least one more object
-                  left.
-        '''
-        return self._msgs and self._valid(self._msgs)
-
-    _destroy = nmlib.notmuch_messages_destroy
-    _destroy.argtypes = [NotmuchMessagesP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch Messages"""
-        if self._msgs:
-            self._destroy(self._msgs)
-
-class EmptyMessagesResult(Messages):
-    def __init__(self, parent):
-        self._msgs = None
-        self._parent = parent
-
-    def __next__(self):
-        raise StopIteration()
-    next = __next__
diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py
deleted file mode 100644 (file)
index ffb86df..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-from ctypes import c_char_p, c_uint, POINTER, byref
-from .globals import (
-    nmlib,
-    Enum,
-    _str,
-    NotmuchQueryP,
-    NotmuchThreadsP,
-    NotmuchDatabaseP,
-    NotmuchMessagesP,
-)
-from .errors import (
-    NotmuchError,
-    NullPointerError,
-    NotInitializedError,
-)
-from .threads import Threads
-from .messages import Messages
-
-
-class Query(object):
-    """Represents a search query on an opened :class:`Database`.
-
-    A query selects and filters a subset of messages from the notmuch
-    database we derive from.
-
-    :class:`Query` provides an instance attribute :attr:`sort`, which
-    contains the sort order (if specified via :meth:`set_sort`) or
-    `None`.
-
-    Any function in this class may throw an :exc:`NotInitializedError`
-    in case the underlying query object was not set up correctly.
-
-    .. note:: Do remember that as soon as we tear down this object,
-           all underlying derived objects such as threads,
-           messages, tags etc will be freed by the underlying library
-           as well. Accessing these objects will lead to segfaults and
-           other unexpected behavior. See above for more details.
-    """
-    # constants
-    SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
-    """Constants: Sort order in which to return results"""
-
-    def __init__(self, db, querystr):
-        """
-        :param db: An open database which we derive the Query from.
-        :type db: :class:`Database`
-        :param querystr: The query string for the message.
-        :type querystr: utf-8 encoded str or unicode
-        """
-        self._db = None
-        self._query = None
-        self.sort = None
-        self.create(db, querystr)
-
-    def _assert_query_is_initialized(self):
-        """Raises :exc:`NotInitializedError` if self._query is `None`"""
-        if not self._query:
-            raise NotInitializedError()
-
-    """notmuch_query_create"""
-    _create = nmlib.notmuch_query_create
-    _create.argtypes = [NotmuchDatabaseP, c_char_p]
-    _create.restype = NotmuchQueryP
-
-    def create(self, db, querystr):
-        """Creates a new query derived from a Database
-
-        This function is utilized by __init__() and usually does not need to
-        be called directly.
-
-        :param db: Database to create the query from.
-        :type db: :class:`Database`
-        :param querystr: The query string
-        :type querystr: utf-8 encoded str or unicode
-        :raises:
-            :exc:`NullPointerError` if the query creation failed
-                (e.g. too little memory).
-            :exc:`NotInitializedError` if the underlying db was not
-                initialized.
-        """
-        db._assert_db_is_initialized()
-        # create reference to parent db to keep it alive
-        self._db = db
-        # create query, return None if too little mem available
-        query_p = Query._create(db._db, _str(querystr))
-        if not query_p:
-            raise NullPointerError
-        self._query = query_p
-
-    _set_sort = nmlib.notmuch_query_set_sort
-    _set_sort.argtypes = [NotmuchQueryP, c_uint]
-    _set_sort.restype = None
-
-    def set_sort(self, sort):
-        """Set the sort order future results will be delivered in
-
-        :param sort: Sort order (see :attr:`Query.SORT`)
-        """
-        self._assert_query_is_initialized()
-        self.sort = sort
-        self._set_sort(self._query, sort)
-
-    _exclude_tag = nmlib.notmuch_query_add_tag_exclude
-    _exclude_tag.argtypes = [NotmuchQueryP, c_char_p]
-    _exclude_tag.restype = None
-
-    def exclude_tag(self, tagname):
-        """Add a tag that will be excluded from the query results by default.
-
-        This exclusion will be overridden if this tag appears explicitly in the
-        query.
-
-        :param tagname: Name of the tag to be excluded
-        """
-        self._assert_query_is_initialized()
-        self._exclude_tag(self._query, _str(tagname))
-
-    """notmuch_query_search_threads"""
-    _search_threads = nmlib.notmuch_query_search_threads
-    _search_threads.argtypes = [NotmuchQueryP, POINTER(NotmuchThreadsP)]
-    _search_threads.restype = c_uint
-
-    def search_threads(self):
-        r"""Execute a query for threads
-
-        Execute a query for threads, returning a :class:`Threads` iterator.
-        The returned threads are owned by the query and as such, will only be
-        valid until the Query is deleted.
-
-        The method sets :attr:`Message.FLAG`\.MATCH for those messages that
-        match the query. The method :meth:`Message.get_flag` allows us
-        to get the value of this flag.
-
-        :returns: :class:`Threads`
-        :raises: :exc:`NullPointerError` if search_threads failed
-        """
-        self._assert_query_is_initialized()
-        threads_p = NotmuchThreadsP() # == NULL
-        status = Query._search_threads(self._query, byref(threads_p))
-        if status != 0:
-            raise NotmuchError(status)
-
-        if not threads_p:
-            raise NullPointerError
-        return Threads(threads_p, self)
-
-    """notmuch_query_search_messages_st"""
-    _search_messages = nmlib.notmuch_query_search_messages
-    _search_messages.argtypes = [NotmuchQueryP, POINTER(NotmuchMessagesP)]
-    _search_messages.restype = c_uint
-
-    def search_messages(self):
-        """Filter messages according to the query and return
-        :class:`Messages` in the defined sort order
-
-        :returns: :class:`Messages`
-        :raises: :exc:`NullPointerError` if search_messages failed
-        """
-        self._assert_query_is_initialized()
-        msgs_p = NotmuchMessagesP() # == NULL
-        status = Query._search_messages(self._query, byref(msgs_p))
-        if status != 0:
-            raise NotmuchError(status)
-
-        if not msgs_p:
-            raise NullPointerError
-        return Messages(msgs_p, self)
-
-    _count_messages = nmlib.notmuch_query_count_messages
-    _count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
-    _count_messages.restype = c_uint
-
-    def count_messages(self):
-        '''
-        This function performs a search and returns Xapian's best
-        guess as to the number of matching messages.
-
-        :returns: the estimated number of messages matching this query
-        :rtype:   int
-        '''
-        self._assert_query_is_initialized()
-        count = c_uint(0)
-        status = Query._count_messages(self._query, byref(count))
-        if status != 0:
-            raise NotmuchError(status)
-        return count.value
-
-    _count_threads = nmlib.notmuch_query_count_threads
-    _count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
-    _count_threads.restype = c_uint
-
-    def count_threads(self):
-        '''
-        This function performs a search and returns the number of
-        unique thread IDs in the matching messages. This is the same
-        as number of threads matching a search.
-
-        Note that this is a significantly heavier operation than
-        meth:`Query.count_messages`.
-
-        :returns: the number of threads returned by this query
-        :rtype:   int
-        '''
-        self._assert_query_is_initialized()
-        count = c_uint(0)
-        status = Query._count_threads(self._query, byref(count))
-        if status != 0:
-            raise NotmuchError(status)
-        return count.value
-
-    _destroy = nmlib.notmuch_query_destroy
-    _destroy.argtypes = [NotmuchQueryP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the Query"""
-        if self._query:
-            self._destroy(self._query)
diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py
deleted file mode 100644 (file)
index fbb18ce..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-from ctypes import c_char_p
-from .globals import (
-    nmlib,
-    Python3StringMixIn,
-    NotmuchTagsP,
-)
-from .errors import (
-    NullPointerError,
-    NotInitializedError,
-)
-
-
-class Tags(Python3StringMixIn):
-    """Represents a list of notmuch tags
-
-    This object provides an iterator over a list of notmuch tags (which
-    are unicode instances).
-
-    Do note that the underlying library only provides a one-time
-    iterator (it cannot reset the iterator to the start). Thus iterating
-    over the function will "exhaust" the list of tags, and a subsequent
-    iteration attempt will raise a :exc:`NotInitializedError`.
-    Also note, that any function that uses iteration (nearly all) will
-    also exhaust the tags. So both::
-
-      for tag in tags: print tag
-
-    as well as::
-
-       number_of_tags = len(tags)
-
-    and even a simple::
-
-       #str() iterates over all tags to construct a space separated list
-       print(str(tags))
-
-    will "exhaust" the Tags. If you need to re-iterate over a list of
-    tags you will need to retrieve a new :class:`Tags` object.
-    """
-
-    #notmuch_tags_get
-    _get = nmlib.notmuch_tags_get
-    _get.argtypes = [NotmuchTagsP]
-    _get.restype = c_char_p
-
-    def __init__(self, tags_p, parent=None):
-        """
-        :param tags_p: A pointer to an underlying *notmuch_tags_t*
-             structure. These are not publicly exposed, so a user
-             will almost never instantiate a :class:`Tags` object
-             herself. They are usually handed back as a result,
-             e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
-             valid, we will raise an :exc:`NullPointerError` if it is
-             `None`.
-        :type tags_p: :class:`ctypes.c_void_p`
-        :param parent: The parent object (ie :class:`Database` or
-             :class:`Message` these tags are derived from, and saves a
-             reference to it, so we can automatically delete the db object
-             once all derived objects are dead.
-        :TODO: Make the iterator optionally work more than once by
-               cache the tags in the Python object(?)
-        """
-        if not tags_p:
-            raise NullPointerError()
-
-        self._tags = tags_p
-        #save reference to parent object so we keep it alive
-        self._parent = parent
-
-    def __iter__(self):
-        """ Make Tags an iterator """
-        return self
-
-    _valid = nmlib.notmuch_tags_valid
-    _valid.argtypes = [NotmuchTagsP]
-    _valid.restype = bool
-
-    _move_to_next = nmlib.notmuch_tags_move_to_next
-    _move_to_next.argtypes = [NotmuchTagsP]
-    _move_to_next.restype = None
-
-    def __next__(self):
-        if not self._tags:
-            raise NotInitializedError()
-        if not self._valid(self._tags):
-            self._tags = None
-            raise StopIteration
-        tag = Tags._get(self._tags).decode('UTF-8')
-        self._move_to_next(self._tags)
-        return tag
-    next = __next__ # python2.x iterator protocol compatibility
-
-    def __nonzero__(self):
-        '''
-        Implement truth value testing. If __nonzero__ is not
-        implemented, the python runtime would fall back to `len(..) >
-        0` thus exhausting the iterator.
-
-        :returns: True if the wrapped iterator has at least one more object
-                  left.
-        '''
-        return self._tags and self._valid(self._tags)
-
-    def __unicode__(self):
-        """string representation of :class:`Tags`: a space separated list of tags
-
-        .. note::
-
-            As this iterates over the tags, we will not be able to iterate over
-            them again (as in retrieve them)! If the tags have been exhausted
-            already, this will raise a :exc:`NotInitializedError`on subsequent
-            attempts.
-        """
-        return " ".join(self)
-
-    _destroy = nmlib.notmuch_tags_destroy
-    _destroy.argtypes = [NotmuchTagsP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch tags"""
-        if self._tags:
-            self._destroy(self._tags)
diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py
deleted file mode 100644 (file)
index 9aa0e08..0000000
+++ /dev/null
@@ -1,281 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-from ctypes import c_char_p, c_long, c_int
-from .globals import (
-    nmlib,
-    NotmuchThreadP,
-    NotmuchMessagesP,
-    NotmuchTagsP,
-)
-from .errors import (
-    NullPointerError,
-    NotInitializedError,
-)
-from .messages import Messages
-from .tag import Tags
-from datetime import date
-
-class Thread(object):
-    """Represents a single message thread."""
-
-    """notmuch_thread_get_thread_id"""
-    _get_thread_id = nmlib.notmuch_thread_get_thread_id
-    _get_thread_id.argtypes = [NotmuchThreadP]
-    _get_thread_id.restype = c_char_p
-
-    """notmuch_thread_get_authors"""
-    _get_authors = nmlib.notmuch_thread_get_authors
-    _get_authors.argtypes = [NotmuchThreadP]
-    _get_authors.restype = c_char_p
-
-    """notmuch_thread_get_subject"""
-    _get_subject = nmlib.notmuch_thread_get_subject
-    _get_subject.argtypes = [NotmuchThreadP]
-    _get_subject.restype = c_char_p
-
-    """notmuch_thread_get_toplevel_messages"""
-    _get_toplevel_messages = nmlib.notmuch_thread_get_toplevel_messages
-    _get_toplevel_messages.argtypes = [NotmuchThreadP]
-    _get_toplevel_messages.restype = NotmuchMessagesP
-
-    _get_newest_date = nmlib.notmuch_thread_get_newest_date
-    _get_newest_date.argtypes = [NotmuchThreadP]
-    _get_newest_date.restype = c_long
-
-    _get_oldest_date = nmlib.notmuch_thread_get_oldest_date
-    _get_oldest_date.argtypes = [NotmuchThreadP]
-    _get_oldest_date.restype = c_long
-
-    """notmuch_thread_get_tags"""
-    _get_tags = nmlib.notmuch_thread_get_tags
-    _get_tags.argtypes = [NotmuchThreadP]
-    _get_tags.restype = NotmuchTagsP
-
-    def __init__(self, thread_p, parent=None):
-        """
-        :param thread_p: A pointer to an internal notmuch_thread_t
-            Structure.  These are not publicly exposed, so a user
-            will almost never instantiate a :class:`Thread` object
-            herself. They are usually handed back as a result,
-            e.g. when iterating through :class:`Threads`. *thread_p*
-            must be valid, we will raise an :exc:`NullPointerError`
-            if it is `None`.
-
-        :param parent: A 'parent' object is passed which this message is
-              derived from. We save a reference to it, so we can
-              automatically delete the parent object once all derived
-              objects are dead.
-        """
-        if not thread_p:
-            raise NullPointerError()
-        self._thread = thread_p
-        #keep reference to parent, so we keep it alive
-        self._parent = parent
-
-    def get_thread_id(self):
-        """Get the thread ID of 'thread'
-
-        The returned string belongs to 'thread' and will only be valid
-        for as long as the thread is valid.
-
-        :returns: String with a message ID
-        :raises: :exc:`NotInitializedError` if the thread
-                    is not initialized.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore')
-
-    _get_total_messages = nmlib.notmuch_thread_get_total_messages
-    _get_total_messages.argtypes = [NotmuchThreadP]
-    _get_total_messages.restype = c_int
-
-    def get_total_messages(self):
-        """Get the total number of messages in 'thread'
-
-        :returns: The number of all messages in the database
-                  belonging to this thread. Contrast with
-                  :meth:`get_matched_messages`.
-        :raises: :exc:`NotInitializedError` if the thread
-                    is not initialized.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        return self._get_total_messages(self._thread)
-
-    def get_toplevel_messages(self):
-        """Returns a :class:`Messages` iterator for the top-level messages in
-           'thread'
-
-           This iterator will not necessarily iterate over all of the messages
-           in the thread. It will only iterate over the messages in the thread
-           which are not replies to other messages in the thread.
-
-        :returns: :class:`Messages`
-        :raises: :exc:`NotInitializedError` if query is not initialized
-        :raises: :exc:`NullPointerError` if search_messages failed
-        """
-        if not self._thread:
-            raise NotInitializedError()
-
-        msgs_p = Thread._get_toplevel_messages(self._thread)
-
-        if not msgs_p:
-            raise NullPointerError()
-
-        return Messages(msgs_p, self)
-
-    """notmuch_thread_get_messages"""
-    _get_messages = nmlib.notmuch_thread_get_messages
-    _get_messages.argtypes = [NotmuchThreadP]
-    _get_messages.restype = NotmuchMessagesP
-
-    def get_messages(self):
-        """Returns a :class:`Messages` iterator for all messages in 'thread'
-
-        :returns: :class:`Messages`
-        :raises: :exc:`NotInitializedError` if query is not initialized
-        :raises: :exc:`NullPointerError` if get_messages failed
-        """
-        if not self._thread:
-            raise NotInitializedError()
-
-        msgs_p = Thread._get_messages(self._thread)
-
-        if not msgs_p:
-            raise NullPointerError()
-
-        return Messages(msgs_p, self)
-
-    _get_matched_messages = nmlib.notmuch_thread_get_matched_messages
-    _get_matched_messages.argtypes = [NotmuchThreadP]
-    _get_matched_messages.restype = c_int
-
-    def get_matched_messages(self):
-        """Returns the number of messages in 'thread' that matched the query
-
-        :returns: The number of all messages belonging to this thread that
-                  matched the :class:`Query`from which this thread was created.
-                  Contrast with :meth:`get_total_messages`.
-        :raises: :exc:`NotInitializedError` if the thread
-                    is not initialized.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        return self._get_matched_messages(self._thread)
-
-    def get_authors(self):
-        """Returns the authors of 'thread'
-
-        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 returned string belongs to 'thread' and will only be valid for
-        as long as this Thread() is not deleted.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        authors = Thread._get_authors(self._thread)
-        if not authors:
-            return None
-        return authors.decode('UTF-8', 'ignore')
-
-    def get_subject(self):
-        """Returns the Subject of 'thread'
-
-        The returned string belongs to 'thread' and will only be valid for
-        as long as this Thread() is not deleted.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        subject = Thread._get_subject(self._thread)
-        if not subject:
-            return None
-        return subject.decode('UTF-8', 'ignore')
-
-    def get_newest_date(self):
-        """Returns time_t of the newest message date
-
-        :returns: A time_t timestamp.
-        :rtype: c_unit64
-        :raises: :exc:`NotInitializedError` if the message
-                    is not initialized.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        return Thread._get_newest_date(self._thread)
-
-    def get_oldest_date(self):
-        """Returns time_t of the oldest message date
-
-        :returns: A time_t timestamp.
-        :rtype: c_unit64
-        :raises: :exc:`NotInitializedError` if the message
-                    is not initialized.
-        """
-        if not self._thread:
-            raise NotInitializedError()
-        return Thread._get_oldest_date(self._thread)
-
-    def get_tags(self):
-        """ Returns the message tags
-
-        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 :class:`Tags` object is owned by the thread and as such, will only
-        be valid for as long as this :class:`Thread` is valid (e.g. until the
-        query from which it derived is explicitly deleted).
-
-        :returns: A :class:`Tags` iterator.
-        :raises: :exc:`NotInitializedError` if query is not initialized
-        :raises: :exc:`NullPointerError` if search_messages failed
-        """
-        if not self._thread:
-            raise NotInitializedError()
-
-        tags_p = Thread._get_tags(self._thread)
-        if not tags_p:
-            raise NullPointerError()
-        return Tags(tags_p, self)
-
-    def __unicode__(self):
-        frm = "thread:%s %12s [%d/%d] %s; %s (%s)"
-
-        return frm % (self.get_thread_id(),
-                      date.fromtimestamp(self.get_newest_date()),
-                      self.get_matched_messages(),
-                      self.get_total_messages(),
-                      self.get_authors(),
-                      self.get_subject(),
-                      self.get_tags(),
-                     )
-
-    _destroy = nmlib.notmuch_thread_destroy
-    _destroy.argtypes = [NotmuchThreadP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch Thread"""
-        if self._thread:
-            self._destroy(self._thread)
diff --git a/bindings/python/notmuch/threads.py b/bindings/python/notmuch/threads.py
deleted file mode 100644 (file)
index 0c382d5..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
-This file is part of notmuch.
-
-Notmuch is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Notmuch is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-from .globals import (
-    nmlib,
-    Python3StringMixIn,
-    NotmuchThreadP,
-    NotmuchThreadsP,
-)
-from .errors import (
-    NullPointerError,
-    NotInitializedError,
-)
-from .thread import Thread
-
-class Threads(Python3StringMixIn):
-    """Represents a list of notmuch threads
-
-    This object provides an iterator over a list of notmuch threads
-    (Technically, it provides a wrapper for the underlying
-    *notmuch_threads_t* structure). Do note that the underlying
-    library only provides a one-time iterator (it cannot reset the
-    iterator to the start). Thus iterating over the function will
-    "exhaust" the list of threads, and a subsequent iteration attempt
-    will raise a :exc:`NotInitializedError`. Also
-    note, that any function that uses iteration will also
-    exhaust the messages. So both::
-
-      for thread in threads: print thread
-
-    as well as::
-
-       list_of_threads = list(threads)
-
-    will "exhaust" the threads. If you need to re-iterate over a list of
-    messages you will need to retrieve a new :class:`Threads` object.
-
-    Things are not as bad as it seems though, you can store and reuse
-    the single Thread objects as often as you want as long as you
-    keep the parent Threads object around. (Recall that due to
-    hierarchical memory allocation, all derived Threads objects will
-    be invalid when we delete the parent Threads() object, even if it
-    was already "exhausted".) So this works::
-
-      db   = Database()
-      threads = Query(db,'').search_threads() #get a Threads() object
-      threadlist = []
-      for thread in threads:
-         threadlist.append(thread)
-
-      # threads is "exhausted" now.
-      # However it will be kept around until all retrieved Thread() objects are
-      # also deleted. If you did e.g. an explicit del(threads) here, the
-      # following lines would fail.
-
-      # You can reiterate over *threadlist* however as often as you want.
-      # It is simply a list with Thread objects.
-
-      print (threadlist[0].get_thread_id())
-      print (threadlist[1].get_thread_id())
-      print (threadlist[0].get_total_messages())
-    """
-
-    #notmuch_threads_get
-    _get = nmlib.notmuch_threads_get
-    _get.argtypes = [NotmuchThreadsP]
-    _get.restype = NotmuchThreadP
-
-    def __init__(self, threads_p, parent=None):
-        """
-        :param threads_p:  A pointer to an underlying *notmuch_threads_t*
-             structure. These are not publicly exposed, so a user
-             will almost never instantiate a :class:`Threads` object
-             herself. They are usually handed back as a result,
-             e.g. in :meth:`Query.search_threads`.  *threads_p* must be
-             valid, we will raise an :exc:`NullPointerError` if it is
-             `None`.
-        :type threads_p: :class:`ctypes.c_void_p`
-        :param parent: The parent object
-             (ie :class:`Query`) these tags are derived from. It saves
-             a reference to it, so we can automatically delete the db
-             object once all derived objects are dead.
-        :TODO: Make the iterator work more than once and cache the tags in
-               the Python object.(?)
-        """
-        if not threads_p:
-            raise NullPointerError()
-
-        self._threads = threads_p
-        #store parent, so we keep them alive as long as self  is alive
-        self._parent = parent
-
-    def __iter__(self):
-        """ Make Threads an iterator """
-        return self
-
-    _valid = nmlib.notmuch_threads_valid
-    _valid.argtypes = [NotmuchThreadsP]
-    _valid.restype = bool
-
-    _move_to_next = nmlib.notmuch_threads_move_to_next
-    _move_to_next.argtypes = [NotmuchThreadsP]
-    _move_to_next.restype = None
-
-    def __next__(self):
-        if not self._threads:
-            raise NotInitializedError()
-
-        if not self._valid(self._threads):
-            self._threads = None
-            raise StopIteration
-
-        thread = Thread(Threads._get(self._threads), self)
-        self._move_to_next(self._threads)
-        return thread
-    next = __next__ # python2.x iterator protocol compatibility
-
-    def __nonzero__(self):
-        '''
-        Implement truth value testing. If __nonzero__ is not
-        implemented, the python runtime would fall back to `len(..) >
-        0` thus exhausting the iterator.
-
-        :returns: True if the wrapped iterator has at least one more object
-                  left.
-        '''
-        return self._threads and self._valid(self._threads)
-
-    _destroy = nmlib.notmuch_threads_destroy
-    _destroy.argtypes = [NotmuchThreadsP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch Threads"""
-        if self._threads:
-            self._destroy(self._threads)
diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py
deleted file mode 100644 (file)
index 943f335..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-# this file should be kept in sync with ../../../version
-__VERSION__ = '0.37'
-SOVERSION = '5'
diff --git a/bindings/python/setup.py b/bindings/python/setup.py
deleted file mode 100644 (file)
index 6308b9f..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-
-"""
-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 <https://www.gnu.org/licenses/>.
-
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
-"""
-
-import os
-from distutils.core import setup
-
-# get the notmuch version number without importing the notmuch module
-version_file = os.path.join(os.path.dirname(__file__),
-                            'notmuch', 'version.py')
-exec(compile(open(version_file).read(), version_file, 'exec'))
-assert '__VERSION__' in globals(), \
-    'Failed to read the notmuch binding version number'
-
-setup(name='notmuch',
-      version=__VERSION__,
-      description='Python binding of the notmuch mail search and indexing library.',
-      author='Sebastian Spaeth',
-      author_email='Sebastian@SSpaeth.de',
-      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
-<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 <https://notmuch.readthedocs.io/>`_.
-
-Requirements
-------------
-
-You need to have notmuch installed (or rather libnotmuch.so.1). Also,
-notmuch makes use of the ctypes library, and has only been tested with
-python >= 2.5. It will not work on earlier python versions.
-''',
-      classifiers=['Development Status :: 3 - Alpha',
-                   'Intended Audience :: Developers',
-                   'License :: OSI Approved :: GNU General Public License (GPL)',
-                   'Programming Language :: Python :: 2',
-                   'Programming Language :: Python :: 3',
-                   'Topic :: Communications :: Email',
-                   'Topic :: Software Development :: Libraries'
-                   ],
-      platforms='',
-      license='https://www.gnu.org/licenses/gpl-3.0.txt',
-     )
index 9c3dbd96f0b292f856c2ccca2448b8483503a568..ed224ef713bf1f1ed2319678038be5182563d38b 100644 (file)
@@ -58,40 +58,56 @@ notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE self)
     notmuch_database_t *database;
     notmuch_status_t ret;
 
     notmuch_database_t *database;
     notmuch_status_t ret;
 
+    path = NULL;
+    create = 0;
+    mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+
     /* Check arguments */
     /* Check arguments */
-    rb_scan_args (argc, argv, "11", &pathv, &hashv);
+    rb_scan_args (argc, argv, "02", &pathv, &hashv);
 
 
-    SafeStringValue (pathv);
-    path = RSTRING_PTR (pathv);
+    if (!NIL_P (pathv)) {
+       SafeStringValue (pathv);
+       path = RSTRING_PTR (pathv);
+    }
 
     if (!NIL_P (hashv)) {
 
     if (!NIL_P (hashv)) {
-       Check_Type (hashv, T_HASH);
-       create = RTEST (rb_hash_aref (hashv, ID2SYM (ID_db_create)));
-       modev = rb_hash_aref (hashv, ID2SYM (ID_db_mode));
-       if (NIL_P (modev))
-           mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
-       else if (!FIXNUM_P (modev))
-           rb_raise (rb_eTypeError, ":mode isn't a Fixnum");
-       else {
-           mode = FIX2INT (modev);
-           switch (mode) {
-           case NOTMUCH_DATABASE_MODE_READ_ONLY:
-           case NOTMUCH_DATABASE_MODE_READ_WRITE:
-               break;
-           default:
-               rb_raise ( rb_eTypeError, "Invalid mode");
+       VALUE rmode, rcreate;
+       VALUE kwargs[2];
+       static ID keyword_ids[2];
+
+       if (!keyword_ids[0]) {
+           keyword_ids[0] = rb_intern_const ("mode");
+           keyword_ids[1] = rb_intern_const ("create");
+       }
+
+       rb_get_kwargs (hashv, keyword_ids, 0, 2, kwargs);
+
+       rmode = kwargs[0];
+       rcreate = kwargs[1];
+
+       if (rmode != Qundef) {
+           if (!FIXNUM_P (rmode))
+               rb_raise (rb_eTypeError, ":mode isn't a Fixnum");
+           else {
+               mode = FIX2INT (rmode);
+               switch (mode) {
+               case NOTMUCH_DATABASE_MODE_READ_ONLY:
+               case NOTMUCH_DATABASE_MODE_READ_WRITE:
+                   break;
+               default:
+                   rb_raise ( rb_eTypeError, "Invalid mode");
+               }
            }
        }
            }
        }
-    } else {
-       create = 0;
-       mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+       if (rcreate != Qundef)
+           create = RTEST (rcreate);
     }
 
     rb_check_typeddata (self, &notmuch_rb_database_type);
     if (create)
        ret = notmuch_database_create (path, &database);
     else
     }
 
     rb_check_typeddata (self, &notmuch_rb_database_type);
     if (create)
        ret = notmuch_database_create (path, &database);
     else
-       ret = notmuch_database_open (path, mode, &database);
+       ret = notmuch_database_open_with_config (path, mode, NULL, NULL, &database, NULL);
     notmuch_rb_status_raise (ret);
 
     DATA_PTR (self) = notmuch_rb_object_create (database, "notmuch_rb_database");
     notmuch_rb_status_raise (ret);
 
     DATA_PTR (self) = notmuch_rb_object_create (database, "notmuch_rb_database");
@@ -408,7 +424,7 @@ notmuch_rb_database_get_all_tags (VALUE self)
 
        rb_raise (notmuch_rb_eBaseError, "%s", msg);
     }
 
        rb_raise (notmuch_rb_eBaseError, "%s", msg);
     }
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
+    return notmuch_rb_tags_get (tags);
 }
 
 /*
 }
 
 /*
index e2541e8f8de30f6c828be3edbde0699d57760dcc..a2cb38c8a74425ed193144c2607b752210f603f5 100644 (file)
@@ -33,7 +33,6 @@ extern VALUE notmuch_rb_cThreads;
 extern VALUE notmuch_rb_cThread;
 extern VALUE notmuch_rb_cMessages;
 extern VALUE notmuch_rb_cMessage;
 extern VALUE notmuch_rb_cThread;
 extern VALUE notmuch_rb_cMessages;
 extern VALUE notmuch_rb_cMessage;
-extern VALUE notmuch_rb_cTags;
 
 extern VALUE notmuch_rb_eBaseError;
 extern VALUE notmuch_rb_eDatabaseError;
 
 extern VALUE notmuch_rb_eBaseError;
 extern VALUE notmuch_rb_eDatabaseError;
@@ -48,8 +47,6 @@ extern VALUE notmuch_rb_eUnbalancedFreezeThawError;
 extern VALUE notmuch_rb_eUnbalancedAtomicError;
 
 extern ID ID_call;
 extern VALUE notmuch_rb_eUnbalancedAtomicError;
 
 extern ID ID_call;
-extern ID ID_db_create;
-extern ID ID_db_mode;
 
 /* RSTRING_PTR() is new in ruby-1.9 */
 #if !defined(RSTRING_PTR)
 
 /* RSTRING_PTR() is new in ruby-1.9 */
 #if !defined(RSTRING_PTR)
@@ -59,7 +56,6 @@ extern ID ID_db_mode;
 extern const rb_data_type_t notmuch_rb_object_type;
 extern const rb_data_type_t notmuch_rb_database_type;
 extern const rb_data_type_t notmuch_rb_directory_type;
 extern const rb_data_type_t notmuch_rb_object_type;
 extern const rb_data_type_t notmuch_rb_database_type;
 extern const rb_data_type_t notmuch_rb_directory_type;
-extern const rb_data_type_t notmuch_rb_filenames_type;
 extern const rb_data_type_t notmuch_rb_query_type;
 extern const rb_data_type_t notmuch_rb_threads_type;
 extern const rb_data_type_t notmuch_rb_thread_type;
 extern const rb_data_type_t notmuch_rb_query_type;
 extern const rb_data_type_t notmuch_rb_threads_type;
 extern const rb_data_type_t notmuch_rb_thread_type;
@@ -92,9 +88,6 @@ extern const rb_data_type_t notmuch_rb_tags_type;
 #define Data_Get_Notmuch_Directory(obj, ptr) \
     Data_Get_Notmuch_Object ((obj), &notmuch_rb_directory_type, (ptr))
 
 #define Data_Get_Notmuch_Directory(obj, ptr) \
     Data_Get_Notmuch_Object ((obj), &notmuch_rb_directory_type, (ptr))
 
-#define Data_Get_Notmuch_FileNames(obj, ptr) \
-    Data_Get_Notmuch_Object ((obj), &notmuch_rb_filenames_type, (ptr))
-
 #define Data_Get_Notmuch_Query(obj, ptr) \
     Data_Get_Notmuch_Object ((obj), &notmuch_rb_query_type, (ptr))
 
 #define Data_Get_Notmuch_Query(obj, ptr) \
     Data_Get_Notmuch_Object ((obj), &notmuch_rb_query_type, (ptr))
 
@@ -226,10 +219,7 @@ notmuch_rb_directory_get_child_directories (VALUE self);
 
 /* filenames.c */
 VALUE
 
 /* filenames.c */
 VALUE
-notmuch_rb_filenames_destroy (VALUE self);
-
-VALUE
-notmuch_rb_filenames_each (VALUE self);
+notmuch_rb_filenames_get (notmuch_filenames_t *fnames);
 
 /* query.c */
 VALUE
 
 /* query.c */
 VALUE
@@ -370,10 +360,7 @@ notmuch_rb_message_thaw (VALUE self);
 
 /* tags.c */
 VALUE
 
 /* tags.c */
 VALUE
-notmuch_rb_tags_destroy (VALUE self);
-
-VALUE
-notmuch_rb_tags_each (VALUE self);
+notmuch_rb_tags_get (notmuch_tags_t *tags);
 
 /* init.c */
 void
 
 /* init.c */
 void
index 910f0a99f238239f2c5717b2a85b2cbca190bcf5..f267d82f16976ee7fec41779c11d332ab7ac35ad 100644 (file)
@@ -87,7 +87,7 @@ notmuch_rb_directory_get_child_files (VALUE self)
 
     fnames = notmuch_directory_get_child_files (dir);
 
 
     fnames = notmuch_directory_get_child_files (dir);
 
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cFileNames, &notmuch_rb_filenames_type, fnames);
+    return notmuch_rb_filenames_get (fnames);
 }
 
 /*
 }
 
 /*
@@ -106,5 +106,5 @@ notmuch_rb_directory_get_child_directories (VALUE self)
 
     fnames = notmuch_directory_get_child_directories (dir);
 
 
     fnames = notmuch_directory_get_child_directories (dir);
 
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cFileNames, &notmuch_rb_filenames_type, fnames);
+    return notmuch_rb_filenames_get (fnames);
 }
 }
index d914537cff1bbb502e8dd85c1f9e764eed2d1b55..ba97670ffb751977cfca922f457e702b20d464fe 100644 (file)
@@ -5,21 +5,14 @@
 
 require 'mkmf'
 
 
 require 'mkmf'
 
-dir = File.join(ENV['NOTMUCH_SRCDIR'], 'lib')
-
-# includes
-$INCFLAGS = "-I#{dir} #{$INCFLAGS}"
-
-if ENV['EXTRA_LDFLAGS']
-  $LDFLAGS += " " + ENV['EXTRA_LDFLAGS']
-end
+$INCFLAGS = "#{ENV['EXTRA_INCFLAGS']} #{$INCFLAGS}"
+$LDFLAGS = "#{ENV['EXTRA_LDFLAGS']} #{$LDFLAGS}"
 
 if not ENV['LIBNOTMUCH']
   exit 1
 end
 
 
 if not ENV['LIBNOTMUCH']
   exit 1
 end
 
-$LOCAL_LIBS += ENV['LIBNOTMUCH']
-$LIBS += " -ltalloc"
+$LOCAL_LIBS = ENV['LIBNOTMUCH']
 
 # Create Makefile
 dir_config('notmuch')
 
 # Create Makefile
 dir_config('notmuch')
index 0dec19520b59b0b421df5c7a4cb03826e69d235d..60c3fb8b3b412e121d5cea76b46b11af15b25191 100644 (file)
@@ -1,53 +1,11 @@
-/* The Ruby interface to the notmuch mail library
- *
- * Copyright © 2010 Ali Polatel
- *
- * 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: Ali Polatel <alip@exherbo.org>
- */
-
 #include "defs.h"
 
 #include "defs.h"
 
-/*
- * call-seq: FILENAMES.destroy! => nil
- *
- * Destroys the filenames, freeing all resources allocated for it.
- */
-VALUE
-notmuch_rb_filenames_destroy (VALUE self)
-{
-    notmuch_rb_object_destroy (self, &notmuch_rb_filenames_type);
-
-    return Qnil;
-}
-
-/*
- * call-seq: FILENAMES.each {|item| block } => FILENAMES
- *
- * Calls +block+ once for each element in +self+, passing that element as a
- * parameter.
- */
 VALUE
 VALUE
-notmuch_rb_filenames_each (VALUE self)
+notmuch_rb_filenames_get (notmuch_filenames_t *fnames)
 {
 {
-    notmuch_filenames_t *fnames;
-
-    Data_Get_Notmuch_FileNames (self, fnames);
+    VALUE rb_array = rb_ary_new ();
 
     for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames))
 
     for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames))
-       rb_yield (rb_str_new2 (notmuch_filenames_get (fnames)));
-
-    return self;
+       rb_ary_push (rb_array, rb_str_new2 (notmuch_filenames_get (fnames)));
+    return rb_array;
 }
 }
index cd9f04cd734687da69d2a3b30a511c4f9a3f2c50..10558a3326d584f362f302f914027fda3a65a66b 100644 (file)
 
 VALUE notmuch_rb_cDatabase;
 VALUE notmuch_rb_cDirectory;
 
 VALUE notmuch_rb_cDatabase;
 VALUE notmuch_rb_cDirectory;
-VALUE notmuch_rb_cFileNames;
 VALUE notmuch_rb_cQuery;
 VALUE notmuch_rb_cThreads;
 VALUE notmuch_rb_cThread;
 VALUE notmuch_rb_cMessages;
 VALUE notmuch_rb_cMessage;
 VALUE notmuch_rb_cQuery;
 VALUE notmuch_rb_cThreads;
 VALUE notmuch_rb_cThread;
 VALUE notmuch_rb_cMessages;
 VALUE notmuch_rb_cMessage;
-VALUE notmuch_rb_cTags;
 
 VALUE notmuch_rb_eBaseError;
 VALUE notmuch_rb_eDatabaseError;
 
 VALUE notmuch_rb_eBaseError;
 VALUE notmuch_rb_eDatabaseError;
@@ -43,8 +41,6 @@ VALUE notmuch_rb_eUnbalancedFreezeThawError;
 VALUE notmuch_rb_eUnbalancedAtomicError;
 
 ID ID_call;
 VALUE notmuch_rb_eUnbalancedAtomicError;
 
 ID ID_call;
-ID ID_db_create;
-ID ID_db_mode;
 
 const rb_data_type_t notmuch_rb_object_type = {
     .wrap_struct_name = "notmuch_object",
 
 const rb_data_type_t notmuch_rb_object_type = {
     .wrap_struct_name = "notmuch_object",
@@ -65,13 +61,11 @@ const rb_data_type_t notmuch_rb_object_type = {
 
 define_type (database);
 define_type (directory);
 
 define_type (database);
 define_type (directory);
-define_type (filenames);
 define_type (query);
 define_type (threads);
 define_type (thread);
 define_type (messages);
 define_type (message);
 define_type (query);
 define_type (threads);
 define_type (thread);
 define_type (messages);
 define_type (message);
-define_type (tags);
 
 /*
  * Document-module: Notmuch
 
 /*
  * Document-module: Notmuch
@@ -86,13 +80,11 @@ define_type (tags);
  * the user:
  *
  * - Notmuch::Database
  * the user:
  *
  * - Notmuch::Database
- * - Notmuch::FileNames
  * - Notmuch::Query
  * - Notmuch::Threads
  * - Notmuch::Messages
  * - Notmuch::Thread
  * - Notmuch::Message
  * - Notmuch::Query
  * - Notmuch::Threads
  * - Notmuch::Messages
  * - Notmuch::Thread
  * - Notmuch::Message
- * - Notmuch::Tags
  */
 
 void
  */
 
 void
@@ -101,8 +93,6 @@ Init_notmuch (void)
     VALUE mod;
 
     ID_call = rb_intern ("call");
     VALUE mod;
 
     ID_call = rb_intern ("call");
-    ID_db_create = rb_intern ("create");
-    ID_db_mode = rb_intern ("mode");
 
     mod = rb_define_module ("Notmuch");
 
 
     mod = rb_define_module ("Notmuch");
 
@@ -297,23 +287,13 @@ Init_notmuch (void)
     rb_define_method (notmuch_rb_cDirectory, "child_files", notmuch_rb_directory_get_child_files, 0); /* in directory.c */
     rb_define_method (notmuch_rb_cDirectory, "child_directories", notmuch_rb_directory_get_child_directories, 0); /* in directory.c */
 
     rb_define_method (notmuch_rb_cDirectory, "child_files", notmuch_rb_directory_get_child_files, 0); /* in directory.c */
     rb_define_method (notmuch_rb_cDirectory, "child_directories", notmuch_rb_directory_get_child_directories, 0); /* in directory.c */
 
-    /*
-     * Document-class: Notmuch::FileNames
-     *
-     * Notmuch file names
-     */
-    notmuch_rb_cFileNames = rb_define_class_under (mod, "FileNames", rb_cObject);
-    rb_undef_method (notmuch_rb_cFileNames, "initialize");
-    rb_define_method (notmuch_rb_cFileNames, "destroy!", notmuch_rb_filenames_destroy, 0); /* in filenames.c */
-    rb_define_method (notmuch_rb_cFileNames, "each", notmuch_rb_filenames_each, 0); /* in filenames.c */
-    rb_include_module (notmuch_rb_cFileNames, rb_mEnumerable);
-
     /*
      * Document-class: Notmuch::Query
      *
      * Notmuch query
      */
     notmuch_rb_cQuery = rb_define_class_under (mod, "Query", rb_cObject);
     /*
      * Document-class: Notmuch::Query
      *
      * Notmuch query
      */
     notmuch_rb_cQuery = rb_define_class_under (mod, "Query", rb_cObject);
+    rb_undef_alloc_func(notmuch_rb_cQuery);
     rb_undef_method (notmuch_rb_cQuery, "initialize");
     rb_define_method (notmuch_rb_cQuery, "destroy!", notmuch_rb_query_destroy, 0); /* in query.c */
     rb_define_method (notmuch_rb_cQuery, "sort", notmuch_rb_query_get_sort, 0); /* in query.c */
     rb_undef_method (notmuch_rb_cQuery, "initialize");
     rb_define_method (notmuch_rb_cQuery, "destroy!", notmuch_rb_query_destroy, 0); /* in query.c */
     rb_define_method (notmuch_rb_cQuery, "sort", notmuch_rb_query_get_sort, 0); /* in query.c */
@@ -332,6 +312,7 @@ Init_notmuch (void)
      * Notmuch threads
      */
     notmuch_rb_cThreads = rb_define_class_under (mod, "Threads", rb_cObject);
      * Notmuch threads
      */
     notmuch_rb_cThreads = rb_define_class_under (mod, "Threads", rb_cObject);
+    rb_undef_alloc_func(notmuch_rb_cThreads);
     rb_undef_method (notmuch_rb_cThreads, "initialize");
     rb_define_method (notmuch_rb_cThreads, "destroy!", notmuch_rb_threads_destroy, 0); /* in threads.c */
     rb_define_method (notmuch_rb_cThreads, "each", notmuch_rb_threads_each, 0); /* in threads.c */
     rb_undef_method (notmuch_rb_cThreads, "initialize");
     rb_define_method (notmuch_rb_cThreads, "destroy!", notmuch_rb_threads_destroy, 0); /* in threads.c */
     rb_define_method (notmuch_rb_cThreads, "each", notmuch_rb_threads_each, 0); /* in threads.c */
@@ -343,6 +324,7 @@ Init_notmuch (void)
      * Notmuch messages
      */
     notmuch_rb_cMessages = rb_define_class_under (mod, "Messages", rb_cObject);
      * Notmuch messages
      */
     notmuch_rb_cMessages = rb_define_class_under (mod, "Messages", rb_cObject);
+    rb_undef_alloc_func(notmuch_rb_cMessages);
     rb_undef_method (notmuch_rb_cMessages, "initialize");
     rb_define_method (notmuch_rb_cMessages, "destroy!", notmuch_rb_messages_destroy, 0); /* in messages.c */
     rb_define_method (notmuch_rb_cMessages, "each", notmuch_rb_messages_each, 0); /* in messages.c */
     rb_undef_method (notmuch_rb_cMessages, "initialize");
     rb_define_method (notmuch_rb_cMessages, "destroy!", notmuch_rb_messages_destroy, 0); /* in messages.c */
     rb_define_method (notmuch_rb_cMessages, "each", notmuch_rb_messages_each, 0); /* in messages.c */
@@ -355,6 +337,7 @@ Init_notmuch (void)
      * Notmuch thread
      */
     notmuch_rb_cThread = rb_define_class_under (mod, "Thread", rb_cObject);
      * Notmuch thread
      */
     notmuch_rb_cThread = rb_define_class_under (mod, "Thread", rb_cObject);
+    rb_undef_alloc_func(notmuch_rb_cThread);
     rb_undef_method (notmuch_rb_cThread, "initialize");
     rb_define_method (notmuch_rb_cThread, "destroy!", notmuch_rb_thread_destroy, 0); /* in thread.c */
     rb_define_method (notmuch_rb_cThread, "thread_id", notmuch_rb_thread_get_thread_id, 0); /* in thread.c */
     rb_undef_method (notmuch_rb_cThread, "initialize");
     rb_define_method (notmuch_rb_cThread, "destroy!", notmuch_rb_thread_destroy, 0); /* in thread.c */
     rb_define_method (notmuch_rb_cThread, "thread_id", notmuch_rb_thread_get_thread_id, 0); /* in thread.c */
@@ -374,6 +357,7 @@ Init_notmuch (void)
      * Notmuch message
      */
     notmuch_rb_cMessage = rb_define_class_under (mod, "Message", rb_cObject);
      * Notmuch message
      */
     notmuch_rb_cMessage = rb_define_class_under (mod, "Message", rb_cObject);
+    rb_undef_alloc_func(notmuch_rb_cMessage);
     rb_undef_method (notmuch_rb_cMessage, "initialize");
     rb_define_method (notmuch_rb_cMessage, "destroy!", notmuch_rb_message_destroy, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "message_id", notmuch_rb_message_get_message_id, 0); /* in message.c */
     rb_undef_method (notmuch_rb_cMessage, "initialize");
     rb_define_method (notmuch_rb_cMessage, "destroy!", notmuch_rb_message_destroy, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "message_id", notmuch_rb_message_get_message_id, 0); /* in message.c */
@@ -395,15 +379,4 @@ Init_notmuch (void)
     rb_define_method (notmuch_rb_cMessage, "tags_to_maildir_flags", notmuch_rb_message_tags_to_maildir_flags, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "freeze", notmuch_rb_message_freeze, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "thaw", notmuch_rb_message_thaw, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "tags_to_maildir_flags", notmuch_rb_message_tags_to_maildir_flags, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "freeze", notmuch_rb_message_freeze, 0); /* in message.c */
     rb_define_method (notmuch_rb_cMessage, "thaw", notmuch_rb_message_thaw, 0); /* in message.c */
-
-    /*
-     * Document-class: Notmuch::Tags
-     *
-     * Notmuch tags
-     */
-    notmuch_rb_cTags = rb_define_class_under (mod, "Tags", rb_cObject);
-    rb_undef_method (notmuch_rb_cTags, "initialize");
-    rb_define_method (notmuch_rb_cTags, "destroy!", notmuch_rb_tags_destroy, 0); /* in tags.c */
-    rb_define_method (notmuch_rb_cTags, "each", notmuch_rb_tags_each, 0); /* in tags.c */
-    rb_include_module (notmuch_rb_cTags, rb_mEnumerable);
 }
 }
index f45c95cc5051d8475920c5a59bf07706e829e0be..13c182f6549481b0866fb1789e25e8d874ac8b69 100644 (file)
@@ -120,7 +120,7 @@ notmuch_rb_message_get_filenames (VALUE self)
 
     fnames = notmuch_message_get_filenames (message);
 
 
     fnames = notmuch_message_get_filenames (message);
 
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cFileNames, &notmuch_rb_filenames_type, fnames);
+    return notmuch_rb_filenames_get (fnames);
 }
 
 /*
 }
 
 /*
@@ -221,7 +221,7 @@ notmuch_rb_message_get_tags (VALUE self)
     if (!tags)
        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
 
     if (!tags)
        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
 
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
+    return notmuch_rb_tags_get (tags);
 }
 
 /*
 }
 
 /*
index ca5b10d04b09702629eddbeb1eb217bef84a4274..6369d0523008b6dadda38bc58cde3c8c908badf5 100644 (file)
@@ -71,5 +71,5 @@ notmuch_rb_messages_collect_tags (VALUE self)
     if (!tags)
        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
 
     if (!tags)
        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
 
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
+    return notmuch_rb_tags_get (tags);
 }
 }
index 8a2b4d3d48350e6b37ea9de73d86927ddb80e301..077def02f63021336de66d2c8c8e68edf1b4e6c4 100644 (file)
@@ -45,7 +45,7 @@ notmuch_rb_query_get_sort (VALUE self)
 
     Data_Get_Notmuch_Query (self, query);
 
 
     Data_Get_Notmuch_Query (self, query);
 
-    return FIX2INT (notmuch_query_get_sort (query));
+    return INT2FIX (notmuch_query_get_sort (query));
 }
 
 /*
 }
 
 /*
index 2af85e36598220f1cb93e806a8080ed2c3a3b862..b64874d1d4f441aabe8851338633871e8018886d 100644 (file)
@@ -1,56 +1,13 @@
-/* The Ruby interface to the notmuch mail library
- *
- * Copyright © 2010, 2011 Ali Polatel
- *
- * 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: Ali Polatel <alip@exherbo.org>
- */
-
 #include "defs.h"
 
 #include "defs.h"
 
-/*
- * call-seq: TAGS.destroy! => nil
- *
- * Destroys the tags, freeing all resources allocated for it.
- */
-VALUE
-notmuch_rb_tags_destroy (VALUE self)
-{
-    notmuch_rb_object_destroy (self, &notmuch_rb_tags_type);
-
-    return Qnil;
-}
-
-/*
- * call-seq: TAGS.each {|item| block } => TAGS
- *
- * Calls +block+ once for each element in +self+, passing that element as a
- * parameter.
- */
 VALUE
 VALUE
-notmuch_rb_tags_each (VALUE self)
+notmuch_rb_tags_get (notmuch_tags_t *tags)
 {
 {
-    const char *tag;
-    notmuch_tags_t *tags;
-
-    Data_Get_Notmuch_Tags (self, tags);
+    VALUE rb_array = rb_ary_new ();
 
     for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) {
 
     for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) {
-       tag = notmuch_tags_get (tags);
-       rb_yield (rb_str_new2 (tag));
+       const char *tag = notmuch_tags_get (tags);
+       rb_ary_push (rb_array, rb_str_new2 (tag));
     }
     }
-
-    return self;
+    return rb_array;
 }
 }
index 7cb2a3dcc6ebeacdd5c4c37b726a53056c436a5c..b20ed89376f802626d5cb4b0d847b020c18de196 100644 (file)
@@ -204,5 +204,5 @@ notmuch_rb_thread_get_tags (VALUE self)
     if (!tags)
        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
 
     if (!tags)
        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
 
-    return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
+    return notmuch_rb_tags_get (tags);
 }
 }
index 3cd1838d950bd30c6239053a09d2a8f401f8b537..8e004572dbf04865f538ba8d051b1745ae2fd495 100644 (file)
@@ -1,5 +1,6 @@
 #define _GNU_SOURCE
 #define _GNU_SOURCE
-#include <strings.h>
+#include <strings.h> /* strcasecmp() in POSIX */
+#include <string.h> /* strcasecmp() in *BSD */
 
 int
 main ()
 
 int
 main ()
index 0022b54bff5df41765aecdd640484a6dd53b5e5f..3748846edf8392ef80403bf352e0dd9318a80854 100644 (file)
@@ -530,7 +530,7 @@ _notmuch_show()
     ! $split &&
     case "${cur}" in
        -*)
     ! $split &&
     case "${cur}" in
        -*)
-           local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html ${_notmuch_shared_options}"
+           local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html --limit= --offset= ${_notmuch_shared_options}"
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
            compopt -o nospace
            COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
            ;;
index e207d90b72020f0a94fa46c72702a34a6a1ddc35..0bdd7f772a7ae9be4f85a2b31bb1dc65d91a6864 100644 (file)
@@ -245,6 +245,8 @@ _notmuch_show() {
     '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
     '--body=[output body]:output body content:(true false)' \
     '--include-html[include text/html parts in the output]' \
     '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
     '--body=[output body]:output body content:(true false)' \
     '--include-html[include text/html parts in the output]' \
+    '--limit=[limit the number of displayed results]:limit: ' \
+    '--offset=[skip displaying the first N results]:offset: ' \
     '*::search term:_notmuch_search_term'
 }
 
     '*::search term:_notmuch_search_term'
 }
 
index be69b34d87e967dc7ca7b26b45f1564e343fd58e..7afd08c7dac7822b74cf3315b5ba6adacbdb1de7 100755 (executable)
--- a/configure
+++ b/configure
@@ -422,6 +422,18 @@ else
 fi
 unset test_cmdline
 
 fi
 unset test_cmdline
 
+printf "C compiler supports thread sanitizer... "
+test_cmdline="${CC} ${CFLAGS} ${CPPFLAGS} -fsanitize=thread minimal.c ${LDFLAGS} -o minimal"
+if ${test_cmdline} >/dev/null 2>&1 && ./minimal
+then
+    printf "Yes.\n"
+    have_tsan=1
+else
+    printf "Nope, skipping those tests.\n"
+    have_tsan=0
+fi
+unset test_cmdline
+
 printf "Reading libnotmuch version from source... "
 cat > _libversion.c <<EOF
 #include <stdio.h>
 printf "Reading libnotmuch version from source... "
 cat > _libversion.c <<EOF
 #include <stdio.h>
@@ -541,8 +553,8 @@ version of GPGME.
 Please try to rebuild your version of GMime against a more recent
 version of GPGME (at least GPGME 1.8.0).
 EOF
 Please try to rebuild your version of GMime against a more recent
 version of GPGME (at least GPGME 1.8.0).
 EOF
-       if command -v gpgme-config >/dev/null; then
-           printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)"
+       if GPGME_VERS="$(pkg-config --modversion gpgme || gpgme-config --version)"; then
+           printf 'Your current GPGME development version is: %s\n' "$GPGME_VERS"
        else
            printf 'You do not have the GPGME development libraries installed.\n'
        fi
        else
            printf 'You do not have the GPGME development libraries installed.\n'
        fi
@@ -1590,8 +1602,9 @@ NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}
 NOTMUCH_ZLIB_CFLAGS="${zlib_cflags}"
 NOTMUCH_ZLIB_LDFLAGS="${zlib_ldflags}"
 
 NOTMUCH_ZLIB_CFLAGS="${zlib_cflags}"
 NOTMUCH_ZLIB_LDFLAGS="${zlib_ldflags}"
 
-# Does the C compiler support the address sanitizer
+# Does the C compiler support the sanitizers
 NOTMUCH_HAVE_ASAN=${have_asan}
 NOTMUCH_HAVE_ASAN=${have_asan}
+NOTMUCH_HAVE_TSAN=${have_tsan}
 
 # do we have man pages?
 NOTMUCH_HAVE_MAN=$((have_sphinx))
 
 # do we have man pages?
 NOTMUCH_HAVE_MAN=$((have_sphinx))
index 26996c4ab2ccd1136b56e71e0c9d8c86c21a295a..c7520228782b2cacaf48e3b661c593013741e1ba 100644 (file)
@@ -39,8 +39,6 @@ To *run* notmuch-mutt you will need Perl with the following libraries:
   (Debian package: libmail-box-perl)
 - Mail::Header <https://metacpan.org/pod/Mail::Header>
   (Debian package: libmailtools-perl)
   (Debian package: libmail-box-perl)
 - Mail::Header <https://metacpan.org/pod/Mail::Header>
   (Debian package: libmailtools-perl)
-- String::ShellQuote <https://metacpan.org/pod/String::ShellQuote>
-  (Debian package: libstring-shellquote-perl)
 - Term::ReadLine::Gnu <https://metacpan.org/pod/Term::ReadLine::Gnu>
   (Debian package: libterm-readline-gnu-perl)
 
 - Term::ReadLine::Gnu <https://metacpan.org/pod/Term::ReadLine::Gnu>
   (Debian package: libterm-readline-gnu-perl)
 
index d1e2c084b4208f093f97a47e81169da28a65d9ad..b81252c809a8e3ed2fae56fd8e2bb14a6e56ed09 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # notmuch-mutt - notmuch (of a) helper for Mutt
 #
 #
 # notmuch-mutt - notmuch (of a) helper for Mutt
 #
-# Copyright: © 2011-2015 Stefano Zacchiroli <zack@upsilon.cc>
+# Copyright: Â© 2011-2015 Stefano Zacchiroli <zack@upsilon.cc>
 # License: GNU General Public License (GPL), version 3 or above
 #
 # See the bottom of this file for more documentation.
 # License: GNU General Public License (GPL), version 3 or above
 #
 # See the bottom of this file for more documentation.
@@ -13,11 +13,11 @@ use warnings;
 
 use File::Path;
 use File::Basename;
 
 use File::Path;
 use File::Basename;
+use File::Find;
 use Getopt::Long qw(:config no_getopt_compat);
 use Mail::Header;
 use Mail::Box::Maildir;
 use Pod::Usage;
 use Getopt::Long qw(:config no_getopt_compat);
 use Mail::Header;
 use Mail::Box::Maildir;
 use Pod::Usage;
-use String::ShellQuote;
 use Term::ReadLine;
 use Digest::SHA;
 
 use Term::ReadLine;
 use Digest::SHA;
 
@@ -26,9 +26,53 @@ my $xdg_cache_dir = "$ENV{HOME}/.cache";
 $xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME};
 my $cache_dir = "$xdg_cache_dir/notmuch/mutt";
 
 $xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME};
 my $cache_dir = "$xdg_cache_dir/notmuch/mutt";
 
+sub die_dir($$) {
+    my ($maildir, $error) = @_;
+    die "notmuch-mutt: search cache maildir $maildir $error\n".
+        "Please ensure that the notmuch-mutt search cache Maildir\n".
+        "contains no subfolders or real mail data, only symlinks to mail\n";
+}
+
+sub die_subdir($$$) {
+    my ($maildir, $subdir, $error) = @_;
+    die_dir($maildir, "subdir $subdir $error");
+}
 
 
-# create an empty maildir (if missing) or empty an existing maildir"
-sub empty_maildir($) {
+# check that the search cache maildir is that and not a real maildir
+# otherwise there could be data loss when the search cache is emptied
+sub check_search_cache_maildir($) {
+    my ($maildir) = (@_);
+
+    return unless -e $maildir;
+
+    -d $maildir or die_dir($maildir, 'is not a directory');
+
+    opendir(my $mdh, $maildir) or die_dir($maildir, "cannot be opened: $!");
+    my @contents = grep { !/^\.\.?$/ } readdir $mdh;
+    closedir $mdh;
+
+    my @required = ('cur', 'new', 'tmp');
+    foreach my $d (@required) {
+        -l "$maildir/$d" and die_dir($maildir, "contains symlink $d");
+        -e "$maildir/$d" or die_subdir($maildir, $d, 'is missing');
+        -d "$maildir/$d" or die_subdir($maildir, $d, 'is not a directory');
+        find(sub {
+            $_ eq '.' and return;
+            $_ eq '..' and return;
+            -l $_ or die_subdir($maildir, $d, "contains non-symlink $_");
+        }, "$maildir/$d");
+    }
+
+    my %required = map { $_ => 1 } @required;
+    foreach my $d (@contents) {
+        -l "$maildir/$d" and die_dir( $maildir, "contains symlink $d");
+        -d "$maildir/$d" or die_dir( $maildir, "contains non-directory $d");
+        exists($required{$d}) or die_dir( $maildir, "contains directory $d");
+    }
+}
+
+# create an empty search cache maildir (if missing) or empty existing one
+sub empty_search_cache_maildir($) {
     my ($maildir) = (@_);
     rmtree($maildir) if (-d $maildir);
     my $folder = new Mail::Box::Maildir(folder => $maildir,
     my ($maildir) = (@_);
     rmtree($maildir) if (-d $maildir);
     my $folder = new Mail::Box::Maildir(folder => $maildir,
@@ -46,7 +90,8 @@ sub search($$$) {
     push @args, "--duplicate=1" if $remove_dups;
     push @args, $query;
 
     push @args, "--duplicate=1" if $remove_dups;
     push @args, $query;
 
-    empty_maildir($maildir);
+    check_search_cache_maildir($maildir);
+    empty_search_cache_maildir($maildir);
     open my $pipe, '-|', @args or die "Running @args failed: $!\n";
     while (<$pipe>) {
        chomp;
     open my $pipe, '-|', @args or die "Running @args failed: $!\n";
     while (<$pipe>) {
        chomp;
@@ -121,21 +166,23 @@ sub thread_action($$@) {
 
     my $mid = get_message_id();
     if (! defined $mid) {
 
     my $mid = get_message_id();
     if (! defined $mid) {
-       empty_maildir($results_dir);
        die "notmuch-mutt: cannot find Message-Id, abort.\n";
     }
        die "notmuch-mutt: cannot find Message-Id, abort.\n";
     }
-    my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid");
-    my $tid = `$search_cmd`;   # get thread id
-    chomp($tid);
 
 
-    search($results_dir, $remove_dups, $tid);
+    $mid =~ s/ //g; # notmuch strips spaces before storing Message-Id
+    $mid =~ s/"/""""/g; # escape all double quote characters twice
+
+    search($results_dir, $remove_dups, qq{thread:"{id:""$mid""}"});
 }
 
 sub tag_action(@) {
     my $mid = get_message_id();
     defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n";
 
 }
 
 sub tag_action(@) {
     my $mid = get_message_id();
     defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n";
 
-    system("notmuch", "tag", @_, "--", "id:$mid");
+    $mid =~ s/ //g; # notmuch strips spaces before storing Message-Id
+    $mid =~ s/"/""/g; # escape all double quote characters
+
+    system("notmuch", "tag", @_, "--", qq{id:"$mid"});
 }
 
 sub die_usage() {
 }
 
 sub die_usage() {
diff --git a/contrib/python-legacy/.gitignore b/contrib/python-legacy/.gitignore
new file mode 100644 (file)
index 0000000..601acdd
--- /dev/null
@@ -0,0 +1,4 @@
+*.py[co]
+/docs/build
+/docs/html
+/build/
diff --git a/contrib/python-legacy/MANIFEST.in b/contrib/python-legacy/MANIFEST.in
new file mode 100644 (file)
index 0000000..c83be4b
--- /dev/null
@@ -0,0 +1,2 @@
+include notmuch
+#recursive-include docs/html *
\ No newline at end of file
diff --git a/contrib/python-legacy/README b/contrib/python-legacy/README
new file mode 100644 (file)
index 0000000..5bf076d
--- /dev/null
@@ -0,0 +1,17 @@
+notmuch -- The python interface to notmuch
+==========================================
+
+This module makes the functionality of the notmuch library
+(`https://notmuchmail.org`_) available to python. Successful import of
+this module depends on a libnotmuch.so|dll being available on the
+user's system.
+
+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.io/projects/notmuch-python/
+
+To build the python bindings, do
+
+  python setup.py install --prefix=path/to/your/preferred/location
diff --git a/contrib/python-legacy/docs/COPYING b/contrib/python-legacy/docs/COPYING
new file mode 100644 (file)
index 0000000..e600086
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ 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.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, 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
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU 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
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state 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 program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+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
+<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
+<https://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/contrib/python-legacy/docs/Makefile b/contrib/python-legacy/docs/Makefile
new file mode 100644 (file)
index 0000000..bd6de38
--- /dev/null
@@ -0,0 +1,88 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html      to make standalone HTML files"
+       @echo "  dirhtml   to make HTML files named index.html in directories"
+       @echo "  pickle    to make pickle files"
+       @echo "  json      to make JSON files"
+       @echo "  htmlhelp  to make HTML files and a HTML help project"
+       @echo "  qthelp    to make HTML files and a qthelp project"
+       @echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  changes   to make an overview of all changed/added/deprecated items"
+       @echo "  linkcheck to check all external links for integrity"
+       @echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       -rm -rf build/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html
+       @echo
+       @echo "Build finished. The HTML pages are in html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in build/dirhtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in build/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in build/qthelp, like this:"
+       @echo "# qcollectiongenerator build/qthelp/pyDNS.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile build/qthelp/pyDNS.qhc"
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in build/latex."
+       @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+             "run these through (pdf)latex."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
+       @echo
+       @echo "The overview file is in build/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in build/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in build/doctest/output.txt."
diff --git a/contrib/python-legacy/docs/source/conf.py b/contrib/python-legacy/docs/source/conf.py
new file mode 100644 (file)
index 0000000..bd4c8b1
--- /dev/null
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+#
+# pyDNS documentation build configuration file, created by
+# sphinx-quickstart on Tue Feb  2 10:00:47 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+from unittest.mock import Mock
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0,os.path.abspath('../..'))
+
+MOCK_MODULES = [
+    'ctypes',
+]
+for mod_name in MOCK_MODULES:
+    sys.modules[mod_name] = Mock()
+
+
+from notmuch import __VERSION__,__AUTHOR__
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo']
+autoclass_content = "both"
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'notmuch'
+copyright = u'2010-2012, ' + __AUTHOR__
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = __VERSION__
+# The full version, including alpha/beta/rc tags.
+release = __VERSION__
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = False
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+html_use_modindex = False
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'notmuchdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'notmuch.tex', u'notmuch Documentation',
+   u'notmuch contributors', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
diff --git a/contrib/python-legacy/docs/source/database.rst b/contrib/python-legacy/docs/source/database.rst
new file mode 100644 (file)
index 0000000..660de91
--- /dev/null
@@ -0,0 +1,54 @@
+:class:`Database` -- The underlying notmuch database
+====================================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]])
+
+   .. automethod:: create
+
+   .. automethod:: open(path, status=MODE.READ_ONLY)
+
+   .. automethod:: close
+
+   .. automethod:: get_path
+
+   .. automethod:: get_version
+
+   .. automethod:: needs_upgrade
+
+   .. automethod:: upgrade
+
+   .. automethod:: begin_atomic
+
+   .. automethod:: end_atomic
+
+   .. automethod:: get_directory
+
+   .. automethod:: index_file
+
+   .. automethod:: remove_message
+
+   .. automethod:: find_message
+
+   .. automethod:: find_message_by_filename
+
+   .. automethod:: get_all_tags
+
+   .. automethod:: create_query
+
+   .. automethod:: get_config
+
+   .. automethod:: get_configs
+
+   .. automethod:: set_config
+
+   .. attribute:: Database.MODE
+
+     Defines constants that are used as the mode in which to open a database.
+
+     MODE.READ_ONLY
+       Open the database in read-only mode
+
+     MODE.READ_WRITE
+       Open the database in read-write mode
diff --git a/contrib/python-legacy/docs/source/filesystem.rst b/contrib/python-legacy/docs/source/filesystem.rst
new file mode 100644 (file)
index 0000000..13fe119
--- /dev/null
@@ -0,0 +1,32 @@
+Files and directories
+=====================
+
+.. currentmodule:: notmuch
+
+:class:`Filenames` -- An iterator over filenames
+------------------------------------------------
+
+.. autoclass:: Filenames
+
+   .. method:: Filenames.__len__
+   .. warning::
+      :meth:`__len__` was removed in version 0.22 as it exhausted the
+      iterator and broke list(Filenames()). Use `len(list(names))`
+      instead.
+
+:class:`Directory` -- A directory entry in the database
+-------------------------------------------------------
+
+.. autoclass:: Directory
+
+   .. automethod:: Directory.get_child_files
+
+   .. automethod:: Directory.get_child_directories
+
+   .. automethod:: Directory.get_mtime
+
+   .. automethod:: Directory.set_mtime
+
+   .. autoattribute:: Directory.mtime
+
+   .. autoattribute:: Directory.path
diff --git a/contrib/python-legacy/docs/source/index.rst b/contrib/python-legacy/docs/source/index.rst
new file mode 100644 (file)
index 0000000..bef7e60
--- /dev/null
@@ -0,0 +1,36 @@
+Welcome to :mod:`notmuch`'s documentation
+=========================================
+
+.. currentmodule:: notmuch
+
+The :mod:`notmuch` module provides an interface to the `notmuch
+<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
+:class:`Tags`.
+
+.. moduleauthor:: Sebastian Spaeth <Sebastian@SSpaeth.de>
+
+:License: This module is covered under the GNU GPL v3 (or later).
+
+.. toctree::
+   :maxdepth: 1
+
+   quickstart
+   notes
+   status_and_errors
+   database
+   query
+   messages
+   message
+   tags
+   threads
+   thread
+   filesystem
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/contrib/python-legacy/docs/source/message.rst b/contrib/python-legacy/docs/source/message.rst
new file mode 100644 (file)
index 0000000..b003392
--- /dev/null
@@ -0,0 +1,54 @@
+:class:`Message` -- A single message
+====================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Message
+
+   .. automethod:: get_message_id
+
+   .. automethod:: get_thread_id
+
+   .. automethod:: get_replies
+
+   .. automethod:: get_filename
+
+   .. automethod:: get_filenames
+
+   .. attribute:: FLAG
+
+        FLAG.MATCH
+          This flag is automatically set by a
+         Query.search_threads on those messages that match the
+         query. This allows us to distinguish matches from the rest
+         of the messages in that thread.
+
+   .. automethod:: get_flag
+
+   .. automethod:: set_flag
+
+   .. automethod:: get_date
+
+   .. automethod:: get_header
+
+   .. automethod:: get_tags
+
+   .. automethod:: get_property
+
+   .. automethod:: get_properties
+
+   .. automethod:: maildir_flags_to_tags
+
+   .. automethod:: tags_to_maildir_flags
+
+   .. automethod:: remove_tag
+
+   .. automethod:: add_tag
+
+   .. automethod:: remove_all_tags
+
+   .. automethod:: freeze
+
+   .. automethod:: thaw
+
+   .. automethod:: __str__
diff --git a/contrib/python-legacy/docs/source/messages.rst b/contrib/python-legacy/docs/source/messages.rst
new file mode 100644 (file)
index 0000000..3ccf505
--- /dev/null
@@ -0,0 +1,15 @@
+:class:`Messages` -- A bunch of messages
+========================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Messages
+
+   .. automethod:: collect_tags
+
+   .. method:: __len__()
+
+   .. warning::
+
+      :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
+      list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`.
diff --git a/contrib/python-legacy/docs/source/notes.rst b/contrib/python-legacy/docs/source/notes.rst
new file mode 100644 (file)
index 0000000..a792748
--- /dev/null
@@ -0,0 +1,6 @@
+Interfacing with notmuch
+========================
+
+.. todo:: move the note about talloc out of the code
+
+.. automodule:: notmuch
diff --git a/contrib/python-legacy/docs/source/query.rst b/contrib/python-legacy/docs/source/query.rst
new file mode 100644 (file)
index 0000000..785e984
--- /dev/null
@@ -0,0 +1,43 @@
+:class:`Query` -- A search query
+================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Query
+
+   .. automethod:: create
+
+   .. attribute:: Query.SORT
+
+     Defines constants that are used as the mode in which to open a database.
+
+     SORT.OLDEST_FIRST
+       Sort by message date, oldest first.
+
+     SORT.NEWEST_FIRST
+       Sort by message date, newest first.
+
+     SORT.MESSAGE_ID
+       Sort by email message ID.
+
+     SORT.UNSORTED
+       Do not apply a special sort order (returns results in document id
+       order).
+
+   .. automethod:: set_sort
+
+   .. attribute::  sort
+
+      Instance attribute :attr:`sort` contains the sort order (see
+      :attr:`Query.SORT`) if explicitly specified via
+      :meth:`set_sort`. By default it is set to `None`.
+
+   .. automethod:: exclude_tag
+
+   .. automethod:: search_threads
+
+   .. automethod:: search_messages
+
+   .. automethod:: count_messages
+
+   .. automethod:: count_threads
diff --git a/contrib/python-legacy/docs/source/quickstart.rst b/contrib/python-legacy/docs/source/quickstart.rst
new file mode 100644 (file)
index 0000000..609f42e
--- /dev/null
@@ -0,0 +1,19 @@
+Quickstart and examples
+=======================
+
+.. todo:: write a nice introduction
+.. todo:: improve the examples
+
+Notmuch can be imported as::
+
+    import notmuch
+
+or::
+
+    from notmuch import Query, Database
+
+    db = Database('path', create=True)
+    msgs = Query(db, 'from:myself').search_messages()
+
+    for msg in msgs:
+        print(msg)
diff --git a/contrib/python-legacy/docs/source/status_and_errors.rst b/contrib/python-legacy/docs/source/status_and_errors.rst
new file mode 100644 (file)
index 0000000..68913f1
--- /dev/null
@@ -0,0 +1,57 @@
+.. currentmodule:: notmuch
+
+Status and Errors
+=================
+
+Some methods return a status, indicating if an operation was successful and what the error was. Most of these status codes are expressed as a specific value, the :class:`notmuch.STATUS`.
+
+.. note::
+
+    Prior to version 0.12 the exception classes and the enumeration
+    :class:`notmuch.STATUS` were defined in `notmuch.globals`. They
+    have since then been moved into `notmuch.errors`.
+
+:class:`STATUS` -- Notmuch operation return value
+--------------------------------------------------
+
+.. autoclass:: notmuch.STATUS
+   :inherited-members:
+
+.. automethod:: notmuch.STATUS.status2str
+
+:exc:`NotmuchError` -- A Notmuch execution error
+------------------------------------------------
+Whenever an error occurs, we throw a special Exception :exc:`NotmuchError`, or a more fine grained Exception which is derived from it. This means it is always safe to check for NotmuchErrors if you want to catch all errors. If you are interested in more fine grained exceptions, you can use those below.
+
+.. autoexception:: NotmuchError
+
+The following exceptions are all directly derived from NotmuchError. Each of them corresponds to a specific :class:`notmuch.STATUS` value. You can either check the :attr:`status` attribute of a NotmuchError to see if a specific error has occurred, or you can directly check for the following Exception types:
+
+.. autoexception:: OutOfMemoryError(message=None)
+   :members:
+.. autoexception:: ReadOnlyDatabaseError(message=None)
+   :members:
+.. autoexception:: XapianError(message=None)
+   :members:
+.. autoexception:: FileError(message=None)
+   :members:
+.. autoexception:: FileNotEmailError(message=None)
+   :members:
+.. autoexception:: DuplicateMessageIdError(message=None)
+   :members:
+.. autoexception:: NullPointerError(message=None)
+   :members:
+.. autoexception:: TagTooLongError(message=None)
+   :members:
+.. autoexception:: UnbalancedFreezeThawError(message=None)
+   :members:
+.. autoexception:: UnbalancedAtomicError(message=None)
+   :members:
+.. autoexception:: UnsupportedOperationError(message=None)
+   :members:
+.. autoexception:: UpgradeRequiredError(message=None)
+   :members:
+.. autoexception:: PathError(message=None)
+   :members:
+.. autoexception:: NotInitializedError(message=None)
+   :members:
diff --git a/contrib/python-legacy/docs/source/tags.rst b/contrib/python-legacy/docs/source/tags.rst
new file mode 100644 (file)
index 0000000..31527d4
--- /dev/null
@@ -0,0 +1,17 @@
+:class:`Tags` -- Notmuch tags
+-----------------------------
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Tags
+   :members:
+
+   .. method:: __len__
+
+       .. warning::
+
+            :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
+            list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of
+            tags.
+
+   .. automethod:: __str__
diff --git a/contrib/python-legacy/docs/source/thread.rst b/contrib/python-legacy/docs/source/thread.rst
new file mode 100644 (file)
index 0000000..4067872
--- /dev/null
@@ -0,0 +1,26 @@
+:class:`Thread` -- A single thread
+==================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Thread
+
+  .. automethod:: get_thread_id
+
+  .. automethod:: get_total_messages
+
+  .. automethod:: get_toplevel_messages
+
+  .. automethod:: get_matched_messages
+
+  .. automethod:: get_authors
+
+  .. automethod:: get_subject
+
+  .. automethod:: get_oldest_date
+
+  .. automethod:: get_newest_date
+
+  .. automethod:: get_tags
+
+  .. automethod:: __str__
diff --git a/contrib/python-legacy/docs/source/threads.rst b/contrib/python-legacy/docs/source/threads.rst
new file mode 100644 (file)
index 0000000..46ce5be
--- /dev/null
@@ -0,0 +1,14 @@
+:class:`Threads` -- Threads iterator
+====================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Threads
+
+   .. method:: __len__
+   .. warning::
+      :meth:`__len__` was removed in version 0.22 as it exhausted the
+      iterator and broke list(Threads()). Use `len(list(msgs))`
+      instead.
+
+   .. automethod:: __str__
diff --git a/contrib/python-legacy/notmuch/__init__.py b/contrib/python-legacy/notmuch/__init__.py
new file mode 100644 (file)
index 0000000..cf627ff
--- /dev/null
@@ -0,0 +1,84 @@
+"""The :mod:`notmuch` module provides most of the functionality that a user is
+likely to need.
+
+.. note:: The underlying notmuch library is build on a hierarchical
+    memory allocator called talloc. All objects derive from a
+    top-level :class:`Database` object.
+
+    This means that as soon as an object is deleted, all underlying
+    derived objects such as Queries, Messages, Message, and Tags will
+    be freed by the underlying library as well. Accessing these
+    objects will then lead to segfaults and other unexpected behavior.
+
+    We implement reference counting, so that parent objects can be
+    automatically freed when they are not needed anymore. For
+    example::
+
+            db = Database('path',create=True)
+            msgs = Query(db,'from:myself').search_messages()
+
+    This returns :class:`Messages` which internally contains a
+    reference to its parent :class:`Query` object. Otherwise the
+    Query() would be immediately freed, taking our *msgs* down with
+    it.
+
+    In this case, the above Query() object will be automatically freed
+    whenever we delete all derived objects, ie in our case:
+    `del(msgs)` would also delete the parent Query. It would not
+    delete the parent Database() though, as that is still referenced
+    from the variable *db* in which it is stored.
+
+    Pretty much the same is valid for all other objects in the
+    hierarchy, such as :class:`Query`, :class:`Messages`,
+    :class:`Message`, and :class:`Tags`.
+"""
+
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+from .database import Database
+from .directory import Directory
+from .filenames import Filenames
+from .message import Message
+from .messages import Messages
+from .query import Query
+from .tag import Tags
+from .thread import Thread
+from .threads import Threads
+from .globals import nmlib
+from .errors import (
+    STATUS,
+    NotmuchError,
+    OutOfMemoryError,
+    ReadOnlyDatabaseError,
+    XapianError,
+    FileError,
+    FileNotEmailError,
+    DuplicateMessageIdError,
+    NullPointerError,
+    TagTooLongError,
+    UnbalancedFreezeThawError,
+    UnbalancedAtomicError,
+    NotInitializedError,
+    UnsupportedOperationError,
+    UpgradeRequiredError,
+    PathError,
+)
+from .version import __VERSION__
+__LICENSE__ = "GPL v3+"
+__AUTHOR__ = 'Sebastian Spaeth <Sebastian@SSpaeth.de>'
diff --git a/contrib/python-legacy/notmuch/compat.py b/contrib/python-legacy/notmuch/compat.py
new file mode 100644 (file)
index 0000000..4a94e05
--- /dev/null
@@ -0,0 +1,74 @@
+'''
+This file is part of notmuch.
+
+This module handles differences between python2.x and python3.x and
+allows the notmuch bindings to support both version families with one
+source tree.
+
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+Copyright 2012 Justus Winter <4winter@informatik.uni-hamburg.de>
+'''
+
+import sys
+
+if sys.version_info[0] == 2:
+    from ConfigParser import SafeConfigParser
+
+    class Python3StringMixIn(object):
+        def __str__(self):
+            return unicode(self).encode('utf-8')
+
+    def encode_utf8(value):
+        '''
+        Ensure a nicely utf-8 encoded string to pass to wrapped
+        libnotmuch functions.
+
+        C++ code expects strings to be well formatted and unicode
+        strings to have no null bytes.
+        '''
+        if not isinstance(value, basestring):
+            raise TypeError('Expected str or unicode, got %s' % type(value))
+
+        if isinstance(value, unicode):
+            return value.encode('utf-8', 'replace')
+
+        return value
+else:
+    from configparser import ConfigParser as SafeConfigParser
+
+    if not hasattr(SafeConfigParser, 'readfp'):   # py >= 3.12
+        SafeConfigParser.readfp = SafeConfigParser.read_file
+
+    class Python3StringMixIn(object):
+        def __str__(self):
+            return self.__unicode__()
+
+    def encode_utf8(value):
+        '''
+        Ensure a nicely utf-8 encoded string to pass to wrapped
+        libnotmuch functions.
+
+        C++ code expects strings to be well formatted and unicode
+        strings to have no null bytes.
+        '''
+        if not isinstance(value, str):
+            raise TypeError('Expected str, got %s' % type(value))
+
+        return value.encode('utf-8', 'replace')
+
+# We import the SafeConfigParser class on behalf of other code to cope
+# with the differences between Python 2 and 3.
+SafeConfigParser # avoid warning about unused import
diff --git a/contrib/python-legacy/notmuch/database.py b/contrib/python-legacy/notmuch/database.py
new file mode 100644 (file)
index 0000000..8fb507f
--- /dev/null
@@ -0,0 +1,789 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+import os
+import codecs
+import warnings
+from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
+from .compat import SafeConfigParser
+from .globals import (
+    nmlib,
+    Enum,
+    _str,
+    NotmuchConfigListP,
+    NotmuchDatabaseP,
+    NotmuchDirectoryP,
+    NotmuchIndexoptsP,
+    NotmuchMessageP,
+    NotmuchTagsP,
+)
+from .errors import (
+    STATUS,
+    FileError,
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+)
+from .message import Message
+from .tag import Tags
+from .query import Query
+from .directory import Directory
+
+class Database(object):
+    """The :class:`Database` is the highest-level object that notmuch
+    provides. It references a notmuch database, and can be opened in
+    read-only or read-write mode. A :class:`Query` can be derived from
+    or be applied to a specific database to find messages. Also adding
+    and removing messages to the database happens via this
+    object. Modifications to the database are not atmic by default (see
+    :meth:`begin_atomic`) and once a database has been modified, all
+    other database objects pointing to the same data-base will throw an
+    :exc:`XapianError` as the underlying database has been
+    modified. Close and reopen the database to continue working with it.
+
+    :class:`Database` objects implement the context manager protocol
+    so you can use the :keyword:`with` statement to ensure that the
+    database is properly closed. See :meth:`close` for more
+    information.
+
+    .. note::
+
+        Any function in this class can and will throw an
+        :exc:`NotInitializedError` if the database was not initialized
+        properly.
+    """
+    _std_db_path = None
+    """Class attribute to cache user's default database"""
+
+    MODE = Enum(['READ_ONLY', 'READ_WRITE'])
+    """Constants: Mode in which to open the database"""
+
+    DECRYPTION_POLICY = Enum(['FALSE', 'TRUE', 'AUTO', 'NOSTASH'])
+    """Constants: policies for decrypting messages during indexing"""
+
+    """notmuch_database_get_directory"""
+    _get_directory = nmlib.notmuch_database_get_directory
+    _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)]
+    _get_directory.restype = c_uint
+
+    """notmuch_database_get_path"""
+    _get_path = nmlib.notmuch_database_get_path
+    _get_path.argtypes = [NotmuchDatabaseP]
+    _get_path.restype = c_char_p
+
+    """notmuch_database_get_version"""
+    _get_version = nmlib.notmuch_database_get_version
+    _get_version.argtypes = [NotmuchDatabaseP]
+    _get_version.restype = c_uint
+
+    """notmuch_database_get_revision"""
+    _get_revision = nmlib.notmuch_database_get_revision
+    _get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)]
+    _get_revision.restype = c_uint
+
+    """notmuch_database_open"""
+    _open = nmlib.notmuch_database_open
+    _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)]
+    _open.restype = c_uint
+
+    """notmuch_database_upgrade"""
+    _upgrade = nmlib.notmuch_database_upgrade
+    _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p]
+    _upgrade.restype = c_uint
+
+    """ notmuch_database_find_message"""
+    _find_message = nmlib.notmuch_database_find_message
+    _find_message.argtypes = [NotmuchDatabaseP, c_char_p,
+                              POINTER(NotmuchMessageP)]
+    _find_message.restype = c_uint
+
+    """notmuch_database_find_message_by_filename"""
+    _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
+    _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p,
+                                          POINTER(NotmuchMessageP)]
+    _find_message_by_filename.restype = c_uint
+
+    """notmuch_database_get_all_tags"""
+    _get_all_tags = nmlib.notmuch_database_get_all_tags
+    _get_all_tags.argtypes = [NotmuchDatabaseP]
+    _get_all_tags.restype = NotmuchTagsP
+
+    """notmuch_database_create"""
+    _create = nmlib.notmuch_database_create
+    _create.argtypes = [c_char_p, POINTER(NotmuchDatabaseP)]
+    _create.restype = c_uint
+
+    def __init__(self, path = None, create = False,
+                 mode = MODE.READ_ONLY):
+        """If *path* is `None`, we will try to read a users notmuch
+        configuration and use his configured database. The location of the
+        configuration file can be specified through the environment variable
+        *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
+
+        If *create* is `True`, the database will always be created in
+        :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
+
+        :param path:   Directory to open/create the database in (see
+                       above for behavior if `None`)
+        :type path:    `str` or `None`
+        :param create: Pass `False` to open an existing, `True` to create a new
+                       database.
+        :type create:  bool
+        :param mode:   Mode to open a database in. Is always
+                       :attr:`MODE`.READ_WRITE when creating a new one.
+        :type mode:    :attr:`MODE`
+        :raises: :exc:`NotmuchError` or derived exception in case of
+            failure.
+        """
+        self._db = None
+        self.mode = mode
+        if path is None:
+            # no path specified. use a user's default database
+            if Database._std_db_path is None:
+                #the following line throws a NotmuchError if it fails
+                Database._std_db_path = self._get_user_default_db()
+            path = Database._std_db_path
+
+        if create == False:
+            self.open(path, mode)
+        else:
+            self.create(path)
+
+    _destroy = nmlib.notmuch_database_destroy
+    _destroy.argtypes = [NotmuchDatabaseP]
+    _destroy.restype = c_uint
+
+    def __del__(self):
+        if self._db:
+            status = self._destroy(self._db)
+            if status != STATUS.SUCCESS:
+                raise NotmuchError(status)
+
+    def _assert_db_is_initialized(self):
+        """Raises :exc:`NotInitializedError` if self._db is `None`"""
+        if not self._db:
+            raise NotInitializedError()
+
+    def create(self, path):
+        """Creates a new notmuch database
+
+        This function is used by __init__() and usually does not need
+        to be called directly. It wraps the underlying
+        *notmuch_database_create* function and creates a new notmuch
+        database at *path*. It will always return a database in :attr:`MODE`
+        .READ_WRITE mode as creating an empty database for
+        reading only does not make a great deal of sense.
+
+        :param path: A directory in which we should create the database.
+        :type path: str
+        :raises: :exc:`NotmuchError` in case of any failure
+                    (possibly after printing an error message on stderr).
+        """
+        if self._db:
+            raise NotmuchError(message="Cannot create db, this Database() "
+                                       "already has an open one.")
+
+        db = NotmuchDatabaseP()
+        status = Database._create(_str(path), byref(db))
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        self._db = db
+        return status
+
+    def open(self, path, mode=0):
+        """Opens an existing database
+
+        This function is used by __init__() and usually does not need
+        to be called directly. It wraps the underlying
+        *notmuch_database_open* function.
+
+        :param status: Open the database in read-only or read-write mode
+        :type status:  :attr:`MODE`
+        :raises: Raises :exc:`NotmuchError` in case of any failure
+                    (possibly after printing an error message on stderr).
+        """
+        db = NotmuchDatabaseP()
+        status = Database._open(_str(path), mode, byref(db))
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        self._db = db
+        return status
+
+    _close = nmlib.notmuch_database_close
+    _close.argtypes = [NotmuchDatabaseP]
+    _close.restype = c_uint
+
+    def close(self):
+        '''
+        Closes the notmuch database.
+
+        .. warning::
+
+            This function closes the notmuch database. From that point
+            on every method invoked on any object ever derived from
+            the closed database may cease to function and raise a
+            NotmuchError.
+        '''
+        if self._db:
+            status = self._close(self._db)
+            if status != STATUS.SUCCESS:
+                raise NotmuchError(status)
+
+    def __enter__(self):
+        '''
+        Implements the context manager protocol.
+        '''
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        '''
+        Implements the context manager protocol.
+        '''
+        self.close()
+
+    def get_path(self):
+        """Returns the file path of an open database"""
+        self._assert_db_is_initialized()
+        return Database._get_path(self._db).decode('utf-8')
+
+    def get_version(self):
+        """Returns the database format version
+
+        :returns: The database version as positive integer
+        """
+        self._assert_db_is_initialized()
+        return Database._get_version(self._db)
+
+    def get_revision (self):
+        """Returns the committed database revision and UUID
+
+        :returns: (revision, uuid) The database revision as a positive integer
+        and the UUID of the database.
+        """
+        self._assert_db_is_initialized()
+        uuid = c_char_p ()
+        revision = Database._get_revision(self._db, byref (uuid))
+        return (revision, uuid.value.decode ('utf-8'))
+
+    _needs_upgrade = nmlib.notmuch_database_needs_upgrade
+    _needs_upgrade.argtypes = [NotmuchDatabaseP]
+    _needs_upgrade.restype = bool
+
+    def needs_upgrade(self):
+        """Does this database need to be upgraded before writing to it?
+
+        If this function returns `True` then no functions that modify the
+        database (:meth:`index_file`,
+        :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
+        etc.) will work unless :meth:`upgrade` is called successfully first.
+
+        :returns: `True` or `False`
+        """
+        self._assert_db_is_initialized()
+        return self._needs_upgrade(self._db)
+
+    def upgrade(self):
+        """Upgrades the current database
+
+        After opening a database in read-write mode, the client should
+        check if an upgrade is needed (notmuch_database_needs_upgrade) and
+        if so, upgrade with this function before making any modifications.
+
+        NOT IMPLEMENTED: The optional progress_notify callback can be
+        used by the caller to provide progress indication to the
+        user. If non-NULL it will be called periodically with
+        'progress' as a floating-point value in the range of [0.0..1.0]
+        indicating the progress made so far in the upgrade process.
+
+        :TODO: catch exceptions, document return values and etc...
+        """
+        self._assert_db_is_initialized()
+        status = Database._upgrade(self._db, None, None)
+        # TODO: catch exceptions, document return values and etc
+        return status
+
+    _begin_atomic = nmlib.notmuch_database_begin_atomic
+    _begin_atomic.argtypes = [NotmuchDatabaseP]
+    _begin_atomic.restype = c_uint
+
+    def begin_atomic(self):
+        """Begin an atomic database operation
+
+        Any modifications performed between a successful
+        :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
+        the database atomically.  Note that, unlike a typical database
+        transaction, this only ensures atomicity, not durability;
+        neither begin nor end necessarily flush modifications to disk.
+
+        :returns: :attr:`STATUS`.SUCCESS or raises
+        :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
+                    Xapian exception occurred; atomic section not entered.
+
+        *Added in notmuch 0.9*"""
+        self._assert_db_is_initialized()
+        status = self._begin_atomic(self._db)
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return status
+
+    _end_atomic = nmlib.notmuch_database_end_atomic
+    _end_atomic.argtypes = [NotmuchDatabaseP]
+    _end_atomic.restype = c_uint
+
+    def end_atomic(self):
+        """Indicate the end of an atomic database operation
+
+        See :meth:`begin_atomic` for details.
+
+        :returns: :attr:`STATUS`.SUCCESS or raises
+
+        :raises:
+            :exc:`NotmuchError`:
+                :attr:`STATUS`.XAPIAN_EXCEPTION
+                    A Xapian exception occurred; atomic section not
+                    ended.
+                :attr:`STATUS`.UNBALANCED_ATOMIC:
+                    end_atomic has been called more times than begin_atomic.
+
+        *Added in notmuch 0.9*"""
+        self._assert_db_is_initialized()
+        status = self._end_atomic(self._db)
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return status
+
+    def get_directory(self, path):
+        """Returns a :class:`Directory` of path,
+
+        :param path: An unicode string containing the path relative to the path
+              of database (see :meth:`get_path`), or else should be an absolute
+              path with initial components that match the path of 'database'.
+        :returns: :class:`Directory` or raises an exception.
+        :raises: :exc:`FileError` if path is not relative database or absolute
+                 with initial components same as database.
+        """
+        self._assert_db_is_initialized()
+
+        # sanity checking if path is valid, and make path absolute
+        if path and path[0] == os.sep:
+            # we got an absolute path
+            if not path.startswith(self.get_path()):
+                # but its initial components are not equal to the db path
+                raise FileError('Database().get_directory() called '
+                                'with a wrong absolute path')
+            abs_dirpath = path
+        else:
+            #we got a relative path, make it absolute
+            abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
+
+        dir_p = NotmuchDirectoryP()
+        status = Database._get_directory(self._db, _str(path), byref(dir_p))
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        if not dir_p:
+            return None
+
+        # return the Directory, init it with the absolute path
+        return Directory(abs_dirpath, dir_p, self)
+
+    _get_default_indexopts = nmlib.notmuch_database_get_default_indexopts
+    _get_default_indexopts.argtypes = [NotmuchDatabaseP]
+    _get_default_indexopts.restype = NotmuchIndexoptsP
+
+    _indexopts_set_decrypt_policy = nmlib.notmuch_indexopts_set_decrypt_policy
+    _indexopts_set_decrypt_policy.argtypes = [NotmuchIndexoptsP, c_uint]
+    _indexopts_set_decrypt_policy.restype = None
+
+    _indexopts_destroy = nmlib.notmuch_indexopts_destroy
+    _indexopts_destroy.argtypes = [NotmuchIndexoptsP]
+    _indexopts_destroy.restype = None
+
+    _index_file = nmlib.notmuch_database_index_file
+    _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
+                             c_void_p,
+                             POINTER(NotmuchMessageP)]
+    _index_file.restype = c_uint
+
+    def index_file(self, filename, sync_maildir_flags=False, decrypt_policy=None):
+        """Adds a new message to the database
+
+        :param filename: should be a path relative to the path of the
+            open database (see :meth:`get_path`), or else should be an
+            absolute filename with initial components that match the
+            path of the 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.
+
+        :param sync_maildir_flags: If the message contains Maildir
+            flags, we will -depending on the notmuch configuration- sync
+            those tags to initial notmuch tags, if set to `True`. It is
+            `False` by default to remain consistent with the libnotmuch
+            API. You might want to look into the underlying method
+            :meth:`Message.maildir_flags_to_tags`.
+
+        :param decrypt_policy: If the message contains any encrypted
+            parts, and decrypt_policy is set to
+            :attr:`DECRYPTION_POLICY`.TRUE, notmuch will try to
+            decrypt the message and index the cleartext, stashing any
+            discovered session keys.  If it is set to
+            :attr:`DECRYPTION_POLICY`.FALSE, it will never try to
+            decrypt during indexing.  If it is set to
+            :attr:`DECRYPTION_POLICY`.AUTO, then it will try to use
+            any stashed session keys it knows about, but will not try
+            to access the user's secret keys.
+            :attr:`DECRYPTION_POLICY`.NOSTASH behaves the same as
+            :attr:`DECRYPTION_POLICY`.TRUE except that no session keys
+            are stashed in the database.  If decrypt_policy is set to
+            None (the default), then the database itself will decide
+            whether to decrypt, based on the `index.decrypt`
+            configuration setting (see notmuch-config(1)).
+
+        :returns: On success, we return
+
+           1) a :class:`Message` object that can be used for things
+              such as adding tags to the just-added message.
+           2) one of the following :attr:`STATUS` values:
+
+              :attr:`STATUS`.SUCCESS
+                  Message successfully added to database.
+              :attr:`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 list of the filenames for the existing message.
+
+        :rtype:   2-tuple(:class:`Message`, :attr:`STATUS`)
+
+        :raises: Raises a :exc:`NotmuchError` with the following meaning.
+              If such an exception occurs, nothing was added to the database.
+
+              :attr:`STATUS`.FILE_ERROR
+                      An error occurred trying to open the file, (such as
+                      permission denied, or file not found, etc.).
+              :attr:`STATUS`.FILE_NOT_EMAIL
+                      The contents of filename don't look like an email
+                      message.
+              :attr:`STATUS`.READ_ONLY_DATABASE
+                      Database was opened in read-only mode so no message can
+                      be added.
+        """
+        self._assert_db_is_initialized()
+        msg_p = NotmuchMessageP()
+        indexopts = c_void_p(None)
+        if decrypt_policy is not None:
+            indexopts = self._get_default_indexopts(self._db)
+            self._indexopts_set_decrypt_policy(indexopts, decrypt_policy)
+
+        status = self._index_file(self._db, _str(filename), indexopts, byref(msg_p))
+
+        if indexopts:
+            self._indexopts_destroy(indexopts)
+
+        if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
+            raise NotmuchError(status)
+
+        #construct Message() and return
+        msg = Message(msg_p, self)
+        #automatic sync initial tags from Maildir flags
+        if sync_maildir_flags:
+            msg.maildir_flags_to_tags()
+        return (msg, status)
+
+    def add_message(self, filename, sync_maildir_flags=False):
+        """Deprecated alias for :meth:`index_file`
+        """
+        warnings.warn(
+                "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
+
+        return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
+
+    _remove_message = nmlib.notmuch_database_remove_message
+    _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
+    _remove_message.restype = c_uint
+
+    def remove_message(self, filename):
+        """Removes a message (filename) 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.
+
+        :returns: A :attr:`STATUS` value with the following meaning:
+
+             :attr:`STATUS`.SUCCESS
+               The last filename was removed and the message was removed
+               from the database.
+             :attr:`STATUS`.DUPLICATE_MESSAGE_ID
+               This filename was removed but the message persists in the
+               database with at least one other filename.
+
+        :raises: Raises a :exc:`NotmuchError` with the following meaning.
+             If such an exception occurs, nothing was removed from the
+             database.
+
+             :attr:`STATUS`.READ_ONLY_DATABASE
+               Database was opened in read-only mode so no message can be
+               removed.
+        """
+        self._assert_db_is_initialized()
+        status = self._remove_message(self._db, _str(filename))
+        if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
+            raise NotmuchError(status)
+        return status
+
+    def find_message(self, msgid):
+        """Returns a :class:`Message` as identified by its message ID
+
+        Wraps the underlying *notmuch_database_find_message* function.
+
+        :param msgid: The message ID
+        :type msgid: unicode or str
+        :returns: :class:`Message` or `None` if no message is found.
+        :raises:
+            :exc:`OutOfMemoryError`
+                  If an Out-of-memory occurred while constructing the message.
+            :exc:`XapianError`
+                  In case of a Xapian Exception. These exceptions
+                  include "Database modified" situations, e.g. when the
+                  notmuch database has been modified by another program
+                  in the meantime. In this case, you should close and
+                  reopen the database and retry.
+            :exc:`NotInitializedError` if
+                    the database was not initialized.
+        """
+        self._assert_db_is_initialized()
+        msg_p = NotmuchMessageP()
+        status = Database._find_message(self._db, _str(msgid), byref(msg_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return msg_p and Message(msg_p, self) or None
+
+    def find_message_by_filename(self, filename):
+        """Find a message with the given filename
+
+        :returns: If the database contains a message with the given
+            filename, then a class:`Message:` is returned.  This
+            function returns None if no message is found with the given
+            filename.
+
+        :raises: :exc:`OutOfMemoryError` if an Out-of-memory occurred while
+                 constructing the message.
+        :raises: :exc:`XapianError` in case of a Xapian Exception.
+                 These exceptions include "Database modified"
+                 situations, e.g. when the notmuch database has been
+                 modified by another program in the meantime. In this
+                 case, you should close and reopen the database and
+                 retry.
+        :raises: :exc:`NotInitializedError` if the database was not
+                 initialized.
+
+        *Added in notmuch 0.9*"""
+        self._assert_db_is_initialized()
+
+        msg_p = NotmuchMessageP()
+        status = Database._find_message_by_filename(self._db, _str(filename),
+                                                    byref(msg_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return msg_p and Message(msg_p, self) or None
+
+    def get_all_tags(self):
+        """Returns :class:`Tags` with a list of all tags found in the database
+
+        :returns: :class:`Tags`
+        :exception: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
+                    on error
+        """
+        self._assert_db_is_initialized()
+        tags_p = Database._get_all_tags(self._db)
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def create_query(self, querystring):
+        """Returns a :class:`Query` derived from this database
+
+        This is a shorthand method for doing::
+
+          # short version
+          # Automatically frees the Database() when 'q' is deleted
+
+          q  = Database(dbpath).create_query('from:"Biene Maja"')
+
+          # long version, which is functionally equivalent but will keep the
+          # Database in the 'db' variable around after we delete 'q':
+
+          db = Database(dbpath)
+          q  = Query(db,'from:"Biene Maja"')
+
+        This function is a python extension and not in the underlying C API.
+        """
+        return Query(self, querystring)
+
+    """notmuch_database_status_string"""
+    _status_string = nmlib.notmuch_database_status_string
+    _status_string.argtypes = [NotmuchDatabaseP]
+    _status_string.restype = c_char_p
+
+    def status_string(self):
+        """Returns the status string of the database
+
+        This is sometimes used for additional error reporting
+        """
+        self._assert_db_is_initialized()
+        s = Database._status_string(self._db)
+        if s:
+            return s.decode('utf-8', 'ignore')
+        return s
+
+    def __repr__(self):
+        return "'Notmuch DB " + self.get_path() + "'"
+
+    def _get_user_default_db(self):
+        """ Reads a user's notmuch config and returns his db location
+
+        Throws a NotmuchError if it cannot find it"""
+        config = SafeConfigParser()
+        conf_f = os.getenv('NOTMUCH_CONFIG',
+                           os.path.expanduser('~/.notmuch-config'))
+        config.readfp(codecs.open(conf_f, 'r', 'utf-8'))
+        if not config.has_option('database', 'path'):
+            raise NotmuchError(message="No DB path specified"
+                                       " and no user default found")
+        db_path = config.get('database', 'path')
+        if not os.path.isabs(db_path):
+            db_path = os.path.expanduser(os.path.join("~", db_path))
+        return db_path
+
+    """notmuch_database_get_config"""
+    _get_config = nmlib.notmuch_database_get_config
+    _get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)]
+    _get_config.restype = c_uint
+
+    def get_config(self, key):
+        """Return the value of the given config key.
+
+        Note that only config values that are stored in the database are
+        searched and returned.  The config file is not read.
+
+        :param key: the config key under which a value should be looked up, it
+                    should probably be in the form "section.key"
+        :type key:  str
+        :returns:   the config value or the empty string if no value is present
+                    for that key
+        :rtype:     str
+        :raises:    :exc:`NotmuchError` in case of failure.
+
+        """
+        self._assert_db_is_initialized()
+        return_string = c_char_p()
+        status = self._get_config(self._db, _str(key), byref(return_string))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return return_string.value.decode('utf-8')
+
+    """notmuch_database_get_config_list"""
+    _get_config_list = nmlib.notmuch_database_get_config_list
+    _get_config_list.argtypes = [NotmuchDatabaseP, c_char_p,
+                                 POINTER(NotmuchConfigListP)]
+    _get_config_list.restype = c_uint
+
+    _config_list_valid = nmlib.notmuch_config_list_valid
+    _config_list_valid.argtypes = [NotmuchConfigListP]
+    _config_list_valid.restype = bool
+
+    _config_list_key = nmlib.notmuch_config_list_key
+    _config_list_key.argtypes = [NotmuchConfigListP]
+    _config_list_key.restype = c_char_p
+
+    _config_list_value = nmlib.notmuch_config_list_value
+    _config_list_value.argtypes = [NotmuchConfigListP]
+    _config_list_value.restype = c_char_p
+
+    _config_list_move_to_next = nmlib.notmuch_config_list_move_to_next
+    _config_list_move_to_next.argtypes = [NotmuchConfigListP]
+    _config_list_move_to_next.restype = None
+
+    _config_list_destroy = nmlib.notmuch_config_list_destroy
+    _config_list_destroy.argtypes = [NotmuchConfigListP]
+    _config_list_destroy.restype = None
+
+    def get_configs(self, prefix=''):
+        """Return a generator of key, value pairs where the start of key
+        matches the given prefix
+
+        Note that only config values that are stored in the database are
+        searched and returned.  The config file is not read.  If no `prefix` is
+        given all config values are returned.
+
+        This could be used to get all named queries into a dict for example::
+
+            queries = {k[6:]: v for k, v in db.get_configs('query.')}
+
+        :param prefix: a string by which the keys should be selected
+        :type prefix:  str
+        :yields:       all key-value pairs where `prefix` matches the beginning
+                       of the key
+        :ytype:        pairs of str
+        :raises:      :exc:`NotmuchError` in case of failure.
+
+        """
+        self._assert_db_is_initialized()
+        config_list_p = NotmuchConfigListP()
+        status = self._get_config_list(self._db, _str(prefix),
+                                       byref(config_list_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        while self._config_list_valid(config_list_p):
+            key = self._config_list_key(config_list_p).decode('utf-8')
+            value = self._config_list_value(config_list_p).decode('utf-8')
+            yield key, value
+            self._config_list_move_to_next(config_list_p)
+
+    """notmuch_database_set_config"""
+    _set_config = nmlib.notmuch_database_set_config
+    _set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p]
+    _set_config.restype = c_uint
+
+    def set_config(self, key, value):
+        """Set a config value in the notmuch database.
+
+        If an empty string is provided as `value` the `key` is unset!
+
+        :param key:   the key to set
+        :type key:    str
+        :param value: the value to store under `key`
+        :type value:  str
+        :returns:     None
+        :raises:      :exc:`NotmuchError` in case of failure.
+
+        """
+        self._assert_db_is_initialized()
+        status = self._set_config(self._db, _str(key), _str(value))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
diff --git a/contrib/python-legacy/notmuch/directory.py b/contrib/python-legacy/notmuch/directory.py
new file mode 100644 (file)
index 0000000..b30c9e3
--- /dev/null
@@ -0,0 +1,185 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_uint, c_long
+from .globals import (
+    nmlib,
+    NotmuchDirectoryP,
+    NotmuchFilenamesP
+)
+from .errors import (
+    STATUS,
+    NotmuchError,
+    NotInitializedError,
+)
+from .filenames import Filenames
+
+class Directory(object):
+    """Represents a directory entry in the notmuch directory
+
+    Modifying attributes of this object will modify the
+    database, not the real directory attributes.
+
+    The Directory object is usually derived from another object
+    e.g. via :meth:`Database.get_directory`, and will automatically be
+    become invalid whenever that parent is deleted. You should
+    therefore initialized this object handing it a reference to the
+    parent, preventing the parent from automatically being garbage
+    collected.
+    """
+
+    """notmuch_directory_get_mtime"""
+    _get_mtime = nmlib.notmuch_directory_get_mtime
+    _get_mtime.argtypes = [NotmuchDirectoryP]
+    _get_mtime.restype = c_long
+
+    """notmuch_directory_set_mtime"""
+    _set_mtime = nmlib.notmuch_directory_set_mtime
+    _set_mtime.argtypes = [NotmuchDirectoryP, c_long]
+    _set_mtime.restype = c_uint
+
+    """notmuch_directory_get_child_files"""
+    _get_child_files = nmlib.notmuch_directory_get_child_files
+    _get_child_files.argtypes = [NotmuchDirectoryP]
+    _get_child_files.restype = NotmuchFilenamesP
+
+    """notmuch_directory_get_child_directories"""
+    _get_child_directories = nmlib.notmuch_directory_get_child_directories
+    _get_child_directories.argtypes = [NotmuchDirectoryP]
+    _get_child_directories.restype = NotmuchFilenamesP
+
+    def _assert_dir_is_initialized(self):
+        """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
+        if dir_p is None"""
+        if not self._dir_p:
+            raise NotInitializedError()
+
+    def __init__(self, path, dir_p, parent):
+        """
+        :param path:   The absolute path of the directory object.
+        :param dir_p:  The pointer to an internal notmuch_directory_t object.
+        :param parent: The object this Directory is derived from
+                       (usually a :class:`Database`). We do not directly use
+                       this, but store a reference to it as long as
+                       this Directory object lives. This keeps the
+                       parent object alive.
+        """
+        self._path = path
+        self._dir_p = dir_p
+        self._parent = parent
+
+    def set_mtime(self, mtime):
+        """Sets the mtime value of this directory in the database
+
+        The intention is for the caller to use the mtime to allow efficient
+        identification of new messages to be added to the database. The
+        recommended usage is as follows:
+
+        * Read the mtime of a directory from the filesystem
+
+        * Call :meth:`Database.index_file` for all mail files in
+          the directory
+
+        * Call notmuch_directory_set_mtime with the mtime read from the
+          filesystem.  Then, when wanting to check for updates to the
+          directory in the future, the client can call :meth:`get_mtime`
+          and know that it only needs to add files if the mtime of the
+          directory and files are newer than the stored timestamp.
+
+          .. note::
+
+                :meth:`get_mtime` function does not allow the caller to
+                distinguish a timestamp of 0 from a non-existent timestamp. So
+                don't store a timestamp of 0 unless you are comfortable with
+                that.
+
+        :param mtime: A (time_t) timestamp
+        :raises: :exc:`XapianError` a Xapian exception occurred, mtime
+                 not stored
+        :raises: :exc:`ReadOnlyDatabaseError` the database was opened
+                 in read-only mode so directory mtime cannot be modified
+        :raises: :exc:`NotInitializedError` the directory object has not
+                 been initialized
+        """
+        self._assert_dir_is_initialized()
+        status = Directory._set_mtime(self._dir_p, mtime)
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+    def get_mtime(self):
+        """Gets the mtime value of this directory in the database
+
+        Retrieves a previously stored mtime for this directory.
+
+        :param mtime: A (time_t) timestamp
+        :raises: :exc:`NotmuchError`:
+
+                        :attr:`STATUS`.NOT_INITIALIZED
+                          The directory has not been initialized
+        """
+        self._assert_dir_is_initialized()
+        return Directory._get_mtime(self._dir_p)
+
+    # Make mtime attribute a property of Directory()
+    mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
+                     and setting of the Directory *mtime* (read-write)
+
+                     See :meth:`get_mtime` and :meth:`set_mtime` for usage and
+                     possible exceptions.""")
+
+    def get_child_files(self):
+        """Gets a Filenames iterator listing all the filenames of
+        messages in the database within the given directory.
+
+        The returned filenames will be the basename-entries only (not
+        complete paths.
+        """
+        self._assert_dir_is_initialized()
+        files_p = Directory._get_child_files(self._dir_p)
+        return Filenames(files_p, self)
+
+    def get_child_directories(self):
+        """Gets a :class:`Filenames` iterator listing all the filenames of
+        sub-directories in the database within the given directory
+
+        The returned filenames will be the basename-entries only (not
+        complete paths.
+        """
+        self._assert_dir_is_initialized()
+        files_p = Directory._get_child_directories(self._dir_p)
+        return Filenames(files_p, self)
+
+    @property
+    def path(self):
+        """Returns the absolute path of this Directory (read-only)"""
+        return self._path
+
+    def __repr__(self):
+        """Object representation"""
+        return "<notmuch Directory object '%s'>" % self._path
+
+    _destroy = nmlib.notmuch_directory_destroy
+    _destroy.argtypes = [NotmuchDirectoryP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the Directory"""
+        if self._dir_p:
+            self._destroy(self._dir_p)
diff --git a/contrib/python-legacy/notmuch/errors.py b/contrib/python-legacy/notmuch/errors.py
new file mode 100644 (file)
index 0000000..b7684ef
--- /dev/null
@@ -0,0 +1,204 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_char_p, c_int
+
+from .globals import (
+    nmlib,
+    Enum,
+    Python3StringMixIn,
+)
+
+class Status(Enum):
+    """Enum with a string representation of a notmuch_status_t value."""
+    _status2str = nmlib.notmuch_status_to_string
+    _status2str.restype = c_char_p
+    _status2str.argtypes = [c_int]
+
+    def __init__(self, statuslist):
+        """It is initialized with a list of strings that are available as
+        Status().string1 - Status().stringn attributes.
+        """
+        super(Status, self).__init__(statuslist)
+
+    @classmethod
+    def status2str(self, status):
+        """Get a (unicode) string representation of a notmuch_status_t value."""
+        # define strings for custom error messages
+        if status == STATUS.NOT_INITIALIZED:
+            return "Operation on uninitialized object impossible."
+        return unicode(Status._status2str(status))
+
+STATUS = Status(['SUCCESS',
+  'OUT_OF_MEMORY',
+  'READ_ONLY_DATABASE',
+  'XAPIAN_EXCEPTION',
+  'FILE_ERROR',
+  'FILE_NOT_EMAIL',
+  'DUPLICATE_MESSAGE_ID',
+  'NULL_POINTER',
+  'TAG_TOO_LONG',
+  'UNBALANCED_FREEZE_THAW',
+  'UNBALANCED_ATOMIC',
+  'UNSUPPORTED_OPERATION',
+  'UPGRADE_REQUIRED',
+  'PATH_ERROR',
+  'NOT_INITIALIZED'])
+"""STATUS is a class, whose attributes provide constants that serve as return
+indicators for notmuch functions. Currently the following ones are defined. For
+possible return values and specific meaning for each method, see the method
+description.
+
+  * SUCCESS
+  * OUT_OF_MEMORY
+  * READ_ONLY_DATABASE
+  * XAPIAN_EXCEPTION
+  * FILE_ERROR
+  * FILE_NOT_EMAIL
+  * DUPLICATE_MESSAGE_ID
+  * NULL_POINTER
+  * TAG_TOO_LONG
+  * UNBALANCED_FREEZE_THAW
+  * UNBALANCED_ATOMIC
+  * UNSUPPORTED_OPERATION
+  * UPGRADE_REQUIRED
+  * PATH_ERROR
+  * NOT_INITIALIZED
+
+Invoke the class method `notmuch.STATUS.status2str` with a status value as
+argument to receive a human readable string"""
+STATUS.__name__ = 'STATUS'
+
+
+class NotmuchError(Exception, Python3StringMixIn):
+    """Is initiated with a (notmuch.STATUS[, message=None]). It will not
+    return an instance of the class NotmuchError, but a derived instance
+    of a more specific Error Message, e.g. OutOfMemoryError. Each status
+    but SUCCESS has a corresponding subclassed Exception."""
+
+    @classmethod
+    def get_exc_subclass(cls, status):
+        """Returns a fine grained Exception() type,
+        detailing the error status"""
+        subclasses = {
+            STATUS.OUT_OF_MEMORY: OutOfMemoryError,
+            STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
+            STATUS.XAPIAN_EXCEPTION: XapianError,
+            STATUS.FILE_ERROR: FileError,
+            STATUS.FILE_NOT_EMAIL: FileNotEmailError,
+            STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
+            STATUS.NULL_POINTER: NullPointerError,
+            STATUS.TAG_TOO_LONG: TagTooLongError,
+            STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
+            STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
+            STATUS.UNSUPPORTED_OPERATION: UnsupportedOperationError,
+            STATUS.UPGRADE_REQUIRED: UpgradeRequiredError,
+            STATUS.PATH_ERROR: PathError,
+            STATUS.NOT_INITIALIZED: NotInitializedError,
+        }
+        assert 0 < status <= len(subclasses)
+        return subclasses[status]
+
+    def __new__(cls, *args, **kwargs):
+        """Return a correct subclass of NotmuchError if needed
+
+        We return a NotmuchError instance if status is None (or 0) and a
+        subclass that inherits from NotmuchError depending on the
+        'status' parameter otherwise."""
+        # get 'status'. Passed in as arg or kwarg?
+        status = args[0] if len(args) else kwargs.get('status', None)
+        # no 'status' or cls is subclass already, return 'cls' instance
+        if not status or cls != NotmuchError:
+            return super(NotmuchError, cls).__new__(cls)
+        subclass = cls.get_exc_subclass(status)  # which class to use?
+        return subclass.__new__(subclass, *args, **kwargs)
+
+    def __init__(self, status=None, message=None):
+        self.status = status
+        self.message = message
+
+    def __unicode__(self):
+        if self.message is not None:
+            return self.message
+        elif self.status is not None:
+            return STATUS.status2str(self.status)
+        else:
+            return 'Unknown error'
+
+
+# List of Subclassed exceptions that correspond to STATUS values and are
+# subclasses of NotmuchError.
+class OutOfMemoryError(NotmuchError):
+    status = STATUS.OUT_OF_MEMORY
+
+
+class ReadOnlyDatabaseError(NotmuchError):
+    status = STATUS.READ_ONLY_DATABASE
+
+
+class XapianError(NotmuchError):
+    status = STATUS.XAPIAN_EXCEPTION
+
+
+class FileError(NotmuchError):
+    status = STATUS.FILE_ERROR
+
+
+class FileNotEmailError(NotmuchError):
+    status = STATUS.FILE_NOT_EMAIL
+
+
+class DuplicateMessageIdError(NotmuchError):
+    status = STATUS.DUPLICATE_MESSAGE_ID
+
+
+class NullPointerError(NotmuchError):
+    status = STATUS.NULL_POINTER
+
+
+class TagTooLongError(NotmuchError):
+    status = STATUS.TAG_TOO_LONG
+
+
+class UnbalancedFreezeThawError(NotmuchError):
+    status = STATUS.UNBALANCED_FREEZE_THAW
+
+
+class UnbalancedAtomicError(NotmuchError):
+    status = STATUS.UNBALANCED_ATOMIC
+
+
+class UnsupportedOperationError(NotmuchError):
+    status = STATUS.UNSUPPORTED_OPERATION
+
+
+class UpgradeRequiredError(NotmuchError):
+    status = STATUS.UPGRADE_REQUIRED
+
+
+class PathError(NotmuchError):
+    status = STATUS.PATH_ERROR
+
+
+class NotInitializedError(NotmuchError):
+    """Derived from NotmuchError, this occurs if the underlying data
+    structure (e.g. database is not initialized (yet) or an iterator has
+    been exhausted. You can test for NotmuchError with .status =
+    STATUS.NOT_INITIALIZED"""
+    status = STATUS.NOT_INITIALIZED
diff --git a/contrib/python-legacy/notmuch/filenames.py b/contrib/python-legacy/notmuch/filenames.py
new file mode 100644 (file)
index 0000000..3bbc22b
--- /dev/null
@@ -0,0 +1,131 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+from ctypes import c_char_p
+from .globals import (
+    nmlib,
+    NotmuchFilenamesP,
+    Python3StringMixIn,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+
+
+class Filenames(Python3StringMixIn):
+    """Represents a list of filenames as returned by notmuch
+
+    Objects of this class implement the iterator protocol.
+
+    .. note::
+
+        The underlying library only provides a one-time iterator (it
+        cannot reset the iterator to the start). Thus iterating over
+        the function will "exhaust" the list of tags, and a subsequent
+        iteration attempt will raise a
+        :exc:`NotInitializedError`. Also note, that any function that
+        uses iteration (nearly all) will also exhaust the tags. So
+        both::
+
+           for name in filenames: print name
+
+        as well as::
+
+           list_of_names = list(names)
+
+        and even a simple::
+
+           #str() iterates over all tags to construct a space separated list
+           print(str(filenames))
+
+        will "exhaust" the Filenames. However, you can use
+        :meth:`Message.get_filenames` repeatedly to get fresh
+        Filenames objects to perform various actions on filenames.
+    """
+
+    #notmuch_filenames_get
+    _get = nmlib.notmuch_filenames_get
+    _get.argtypes = [NotmuchFilenamesP]
+    _get.restype = c_char_p
+
+    def __init__(self, files_p, parent):
+        """
+        :param files_p: A pointer to an underlying *notmuch_tags_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Tags` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
+             valid, we will raise an :exc:`NullPointerError`
+             if it is `None`.
+        :type files_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object (ie :class:`Message` these
+             filenames are derived from, and saves a
+             reference to it, so we can automatically delete the db object
+             once all derived objects are dead.
+        """
+        if not files_p:
+            raise NullPointerError()
+
+        self._files_p = files_p
+        #save reference to parent object so we keep it alive
+        self._parent = parent
+
+    def __iter__(self):
+        """ Make Filenames an iterator """
+        return self
+
+    _valid = nmlib.notmuch_filenames_valid
+    _valid.argtypes = [NotmuchFilenamesP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_filenames_move_to_next
+    _move_to_next.argtypes = [NotmuchFilenamesP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._files_p:
+            raise NotInitializedError()
+
+        if not self._valid(self._files_p):
+            self._files_p = None
+            raise StopIteration
+
+        file_ = Filenames._get(self._files_p)
+        self._move_to_next(self._files_p)
+        return file_.decode('utf-8', 'ignore')
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __unicode__(self):
+        """Represent Filenames() as newline-separated list of full paths
+
+        .. note::
+
+            This method exhausts the iterator object, so you will not be able to
+            iterate over them again.
+        """
+        return "\n".join(self)
+
+    _destroy = nmlib.notmuch_filenames_destroy
+    _destroy.argtypes = [NotmuchFilenamesP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch filenames"""
+        if self._files_p:
+            self._destroy(self._files_p)
diff --git a/contrib/python-legacy/notmuch/globals.py b/contrib/python-legacy/notmuch/globals.py
new file mode 100644 (file)
index 0000000..11e328b
--- /dev/null
@@ -0,0 +1,105 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import CDLL, Structure, POINTER
+from notmuch.version import SOVERSION
+
+#-----------------------------------------------------------------------------
+#package-global instance of the notmuch library
+try:
+    from os import uname
+    if uname()[0] == 'Darwin':
+        nmlib = CDLL("libnotmuch.{0:s}.dylib".format(SOVERSION))
+    else:
+        nmlib = CDLL("libnotmuch.so.{0:s}".format(SOVERSION))
+except:
+    raise ImportError("Could not find shared 'notmuch' library.")
+
+from .compat import Python3StringMixIn, encode_utf8 as _str
+
+# We import these on behalf of other modules.  Silence warning about
+# these symbols not being used.
+Python3StringMixIn
+_str
+
+class Enum(object):
+    """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc..."""
+    def __init__(self, names):
+        for number, name in enumerate(names):
+            setattr(self, name, number)
+
+
+class NotmuchDatabaseS(Structure):
+    pass
+NotmuchDatabaseP = POINTER(NotmuchDatabaseS)
+
+
+class NotmuchQueryS(Structure):
+    pass
+NotmuchQueryP = POINTER(NotmuchQueryS)
+
+
+class NotmuchThreadsS(Structure):
+    pass
+NotmuchThreadsP = POINTER(NotmuchThreadsS)
+
+
+class NotmuchThreadS(Structure):
+    pass
+NotmuchThreadP = POINTER(NotmuchThreadS)
+
+
+class NotmuchMessagesS(Structure):
+    pass
+NotmuchMessagesP = POINTER(NotmuchMessagesS)
+
+
+class NotmuchMessageS(Structure):
+    pass
+NotmuchMessageP = POINTER(NotmuchMessageS)
+
+
+class NotmuchMessagePropertiesS(Structure):
+    pass
+NotmuchMessagePropertiesP = POINTER(NotmuchMessagePropertiesS)
+
+
+class NotmuchTagsS(Structure):
+    pass
+NotmuchTagsP = POINTER(NotmuchTagsS)
+
+
+class NotmuchDirectoryS(Structure):
+    pass
+NotmuchDirectoryP = POINTER(NotmuchDirectoryS)
+
+
+class NotmuchFilenamesS(Structure):
+    pass
+NotmuchFilenamesP = POINTER(NotmuchFilenamesS)
+
+
+class NotmuchConfigListS(Structure):
+    pass
+NotmuchConfigListP = POINTER(NotmuchConfigListS)
+
+
+class NotmuchIndexoptsS(Structure):
+    pass
+NotmuchIndexoptsP = POINTER(NotmuchIndexoptsS)
diff --git a/contrib/python-legacy/notmuch/message.py b/contrib/python-legacy/notmuch/message.py
new file mode 100644 (file)
index 0000000..e71dbe3
--- /dev/null
@@ -0,0 +1,721 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+               Jesse Rosenthal <jrosenthal@jhu.edu>
+"""
+
+
+from ctypes import c_char_p, c_long, c_uint, c_int, POINTER, byref
+from datetime import date
+from .globals import (
+    nmlib,
+    Enum,
+    _str,
+    Python3StringMixIn,
+    NotmuchTagsP,
+    NotmuchMessageP,
+    NotmuchMessagesP,
+    NotmuchMessagePropertiesP,
+    NotmuchFilenamesP,
+)
+from .errors import (
+    STATUS,
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+)
+from .tag import Tags
+from .filenames import Filenames
+
+import email
+import sys
+
+
+class Message(Python3StringMixIn):
+    r"""Represents a single Email message
+
+    Technically, this wraps the underlying *notmuch_message_t*
+    structure. A user will usually not create these objects themselves
+    but get them as search results.
+
+    As it implements :meth:`__cmp__`, it is possible to compare two
+    :class:`Message`\s using `if msg1 == msg2: ...`.
+    """
+
+    """notmuch_message_get_filename (notmuch_message_t *message)"""
+    _get_filename = nmlib.notmuch_message_get_filename
+    _get_filename.argtypes = [NotmuchMessageP]
+    _get_filename.restype = c_char_p
+
+    """return all filenames for a message"""
+    _get_filenames = nmlib.notmuch_message_get_filenames
+    _get_filenames.argtypes = [NotmuchMessageP]
+    _get_filenames.restype = NotmuchFilenamesP
+
+    """notmuch_message_get_flag"""
+    _get_flag = nmlib.notmuch_message_get_flag
+    _get_flag.argtypes = [NotmuchMessageP, c_uint]
+    _get_flag.restype = bool
+
+    """notmuch_message_set_flag"""
+    _set_flag = nmlib.notmuch_message_set_flag
+    _set_flag.argtypes = [NotmuchMessageP, c_uint, c_int]
+    _set_flag.restype = None
+
+    """notmuch_message_get_message_id (notmuch_message_t *message)"""
+    _get_message_id = nmlib.notmuch_message_get_message_id
+    _get_message_id.argtypes = [NotmuchMessageP]
+    _get_message_id.restype = c_char_p
+
+    """notmuch_message_get_thread_id"""
+    _get_thread_id = nmlib.notmuch_message_get_thread_id
+    _get_thread_id.argtypes = [NotmuchMessageP]
+    _get_thread_id.restype = c_char_p
+
+    """notmuch_message_get_replies"""
+    _get_replies = nmlib.notmuch_message_get_replies
+    _get_replies.argtypes = [NotmuchMessageP]
+    _get_replies.restype = NotmuchMessagesP
+
+    """notmuch_message_get_tags (notmuch_message_t *message)"""
+    _get_tags = nmlib.notmuch_message_get_tags
+    _get_tags.argtypes = [NotmuchMessageP]
+    _get_tags.restype = NotmuchTagsP
+
+    _get_date = nmlib.notmuch_message_get_date
+    _get_date.argtypes = [NotmuchMessageP]
+    _get_date.restype = c_long
+
+    _get_header = nmlib.notmuch_message_get_header
+    _get_header.argtypes = [NotmuchMessageP, c_char_p]
+    _get_header.restype = c_char_p
+
+    """notmuch_status_t ..._maildir_flags_to_tags (notmuch_message_t *)"""
+    _tags_to_maildir_flags = nmlib.notmuch_message_tags_to_maildir_flags
+    _tags_to_maildir_flags.argtypes = [NotmuchMessageP]
+    _tags_to_maildir_flags.restype = c_int
+
+    """notmuch_status_t ..._tags_to_maildir_flags (notmuch_message_t *)"""
+    _maildir_flags_to_tags = nmlib.notmuch_message_maildir_flags_to_tags
+    _maildir_flags_to_tags.argtypes = [NotmuchMessageP]
+    _maildir_flags_to_tags.restype = c_int
+
+    """notmuch_message_get_property"""
+    _get_property = nmlib.notmuch_message_get_property
+    _get_property.argtypes = [NotmuchMessageP, c_char_p, POINTER(c_char_p)]
+    _get_property.restype = c_int
+
+    """notmuch_message_get_properties"""
+    _get_properties = nmlib.notmuch_message_get_properties
+    _get_properties.argtypes = [NotmuchMessageP, c_char_p, c_int]
+    _get_properties.restype = NotmuchMessagePropertiesP
+
+    """notmuch_message_properties_valid"""
+    _properties_valid = nmlib.notmuch_message_properties_valid
+    _properties_valid.argtypes = [NotmuchMessagePropertiesP]
+    _properties_valid.restype = bool
+
+    """notmuch_message_properties_value"""
+    _properties_value = nmlib.notmuch_message_properties_value
+    _properties_value.argtypes = [NotmuchMessagePropertiesP]
+    _properties_value.restype = c_char_p
+
+    """notmuch_message_properties_key"""
+    _properties_key = nmlib.notmuch_message_properties_key
+    _properties_key.argtypes = [NotmuchMessagePropertiesP]
+    _properties_key.restype = c_char_p
+
+    """notmuch_message_properties_move_to_next"""
+    _properties_move_to_next = nmlib.notmuch_message_properties_move_to_next
+    _properties_move_to_next.argtypes = [NotmuchMessagePropertiesP]
+    _properties_move_to_next.restype = None
+
+    #Constants: Flags that can be set/get with set_flag
+    FLAG = Enum(['MATCH'])
+
+    def __init__(self, msg_p, parent=None):
+        """
+        :param msg_p: A pointer to an internal notmuch_message_t
+            Structure.  If it is `None`, we will raise an
+            :exc:`NullPointerError`.
+
+        :param parent: A 'parent' object is passed which this message is
+              derived from. We save a reference to it, so we can
+              automatically delete the parent object once all derived
+              objects are dead.
+        """
+        if not msg_p:
+            raise NullPointerError()
+        self._msg = msg_p
+        #keep reference to parent, so we keep it alive
+        self._parent = parent
+
+    def get_message_id(self):
+        """Returns the message ID
+
+        :returns: String with a message ID
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_message_id(self._msg).decode('utf-8', 'ignore')
+
+    def get_thread_id(self):
+        """Returns the thread ID
+
+        The returned string belongs to 'message' will only be valid for as
+        long as the message is valid.
+
+        This function will not return `None` since Notmuch ensures that every
+        message belongs to a single thread.
+
+        :returns: String with a thread ID
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        return Message._get_thread_id(self._msg).decode('utf-8', 'ignore')
+
+    def get_replies(self):
+        """Gets all direct replies to this message as :class:`Messages`
+        iterator
+
+        .. note::
+
+            This call only makes sense if 'message' was ultimately obtained from
+            a :class:`Thread` object, (such as by coming directly from the
+            result of calling :meth:`Thread.get_toplevel_messages` or by any
+            number of subsequent calls to :meth:`get_replies`). If this message
+            was obtained through some non-thread means, (such as by a call to
+            :meth:`Query.search_messages`), then this function will return
+            an empty Messages iterator.
+
+        :returns: :class:`Messages`.
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        msgs_p = Message._get_replies(self._msg)
+
+        from .messages import Messages, EmptyMessagesResult
+
+        if not msgs_p:
+            return EmptyMessagesResult(self)
+
+        return Messages(msgs_p, self)
+
+    def get_date(self):
+        """Returns time_t of the message date
+
+        For the original textual representation of the Date header from the
+        message call notmuch_message_get_header() with a header value of
+        "date".
+
+        :returns: A time_t timestamp.
+        :rtype: c_unit64
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_date(self._msg)
+
+    def get_header(self, header):
+        """Get the value of the specified header.
+
+        The value will be read from the actual message file, not from
+        the notmuch database. The header name is case insensitive.
+
+        Returns an empty string ("") if the message does not contain a
+        header line matching 'header'.
+
+        :param header: The name of the header to be retrieved.
+                       It is not case-sensitive.
+        :type header: str
+        :returns: The header value as string
+        :raises: :exc:`NotInitializedError` if the message is not
+                 initialized
+        :raises: :exc:`NullPointerError` if any error occurred
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        #Returns NULL if any error occurs.
+        header = Message._get_header(self._msg, _str(header))
+        if header == None:
+            raise NullPointerError()
+        return header.decode('UTF-8', 'ignore')
+
+    def get_filename(self):
+        """Returns the file path of the message file
+
+        :returns: Absolute file path & name of the message file
+        :raises: :exc:`NotInitializedError` if the message
+              is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_filename(self._msg).decode('utf-8', 'ignore')
+
+    def get_filenames(self):
+        """Get all filenames for the email corresponding to 'message'
+
+        Returns a Filenames() generator with all absolute filepaths for
+        messages recorded to have the same Message-ID. These files must
+        not necessarily have identical content."""
+        if not self._msg:
+            raise NotInitializedError()
+
+        files_p = Message._get_filenames(self._msg)
+
+        return Filenames(files_p, self)
+
+    def get_flag(self, flag):
+        """Checks whether a specific flag is set for this message
+
+        The method :meth:`Query.search_threads` sets
+        *Message.FLAG.MATCH* for those messages that match the
+        query. This method allows us to get the value of this flag.
+
+        :param flag: One of the :attr:`Message.FLAG` values (currently only
+                     *Message.FLAG.MATCH*
+        :returns: An unsigned int (0/1), indicating whether the flag is set.
+        :raises: :exc:`NotInitializedError` if the message
+              is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_flag(self._msg, flag)
+
+    def set_flag(self, flag, value):
+        """Sets/Unsets a specific flag for this message
+
+        :param flag: One of the :attr:`Message.FLAG` values (currently only
+                     *Message.FLAG.MATCH*
+        :param value: A bool indicating whether to set or unset the flag.
+
+        :raises: :exc:`NotInitializedError` if the message
+              is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        self._set_flag(self._msg, flag, value)
+
+    def get_tags(self):
+        """Returns the message tags
+
+        :returns: A :class:`Tags` iterator.
+        :raises: :exc:`NotInitializedError` if the message is not
+                 initialized
+        :raises: :exc:`NullPointerError` if any error occurred
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        tags_p = Message._get_tags(self._msg)
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    _add_tag = nmlib.notmuch_message_add_tag
+    _add_tag.argtypes = [NotmuchMessageP, c_char_p]
+    _add_tag.restype = c_uint
+
+    def add_tag(self, tag, sync_maildir_flags=False):
+        """Adds a tag to the given message
+
+        Adds a tag to the current message. The maximal tag length is defined in
+        the notmuch library and is currently 200 bytes.
+
+        :param tag: String with a 'tag' to be added.
+
+        :param sync_maildir_flags: If notmuch configuration is set to do
+            this, add maildir flags corresponding to notmuch tags. See
+            underlying method :meth:`tags_to_maildir_flags`. Use False
+            if you want to add/remove many tags on a message without
+            having to physically rename the file every time. Do note,
+            that this will do nothing when a message is frozen, as tag
+            changes will not be committed to the database yet.
+
+        :returns: STATUS.SUCCESS if the tag was successfully added.
+                  Raises an exception otherwise.
+        :raises: :exc:`NullPointerError` if the `tag` argument is NULL
+        :raises: :exc:`TagTooLongError` if the length of `tag` exceeds
+                 Message.NOTMUCH_TAG_MAX)
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._add_tag(self._msg, _str(tag))
+
+        # bail out on failure
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+        if sync_maildir_flags:
+            self.tags_to_maildir_flags()
+        return STATUS.SUCCESS
+
+    _remove_tag = nmlib.notmuch_message_remove_tag
+    _remove_tag.argtypes = [NotmuchMessageP, c_char_p]
+    _remove_tag.restype = c_uint
+
+    def remove_tag(self, tag, sync_maildir_flags=False):
+        """Removes a tag from the given message
+
+        If the message has no such tag, this is a non-operation and
+        will report success anyway.
+
+        :param tag: String with a 'tag' to be removed.
+        :param sync_maildir_flags: If notmuch configuration is set to do
+            this, add maildir flags corresponding to notmuch tags. See
+            underlying method :meth:`tags_to_maildir_flags`. Use False
+            if you want to add/remove many tags on a message without
+            having to physically rename the file every time. Do note,
+            that this will do nothing when a message is frozen, as tag
+            changes will not be committed to the database yet.
+
+        :returns: STATUS.SUCCESS if the tag was successfully removed or if
+                  the message had no such tag.
+                  Raises an exception otherwise.
+        :raises: :exc:`NullPointerError` if the `tag` argument is NULL
+        :raises: :exc:`TagTooLongError` if the length of `tag` exceeds
+                 Message.NOTMUCH_TAG_MAX)
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._remove_tag(self._msg, _str(tag))
+        # bail out on error
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+        if sync_maildir_flags:
+            self.tags_to_maildir_flags()
+        return STATUS.SUCCESS
+
+    _remove_all_tags = nmlib.notmuch_message_remove_all_tags
+    _remove_all_tags.argtypes = [NotmuchMessageP]
+    _remove_all_tags.restype = c_uint
+
+    def remove_all_tags(self, sync_maildir_flags=False):
+        """Removes all tags from the given message.
+
+        See :meth:`freeze` for an example showing how to safely
+        replace tag values.
+
+
+        :param sync_maildir_flags: If notmuch configuration is set to do
+            this, add maildir flags corresponding to notmuch tags. See
+            :meth:`tags_to_maildir_flags`. Use False if you want to
+            add/remove many tags on a message without having to
+            physically rename the file every time. Do note, that this
+            will do nothing when a message is frozen, as tag changes
+            will not be committed to the database yet.
+
+        :returns: STATUS.SUCCESS if the tags were successfully removed.
+                  Raises an exception otherwise.
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._remove_all_tags(self._msg)
+
+        # bail out on error
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+        if sync_maildir_flags:
+            self.tags_to_maildir_flags()
+        return STATUS.SUCCESS
+
+    _freeze = nmlib.notmuch_message_freeze
+    _freeze.argtypes = [NotmuchMessageP]
+    _freeze.restype = c_uint
+
+    def get_property(self, prop):
+        """ Retrieve the value for a single property key
+
+        :param prop: The name of the property to get.
+        :returns: String with the property value or None if there is no such
+                  key. In the case of multiple values for the given key, the
+                  first one is retrieved.
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        value = c_char_p()
+        status = Message._get_property(self._msg, _str(prop), byref(value))
+        if status != 0:
+            raise NotmuchError(status)
+
+        if value is None or value.value is None:
+            return None
+        return value.value.decode('utf-8')
+
+    def get_properties(self, prop="", exact=False):
+        """ Get the properties of the message, returning a generator of
+        name, value pairs.
+
+        The generator will yield once per value. There might be more than one
+        value on each name, so the generator might yield the same name several
+        times.
+
+        :param prop: The name of the property to get. Otherwise it will return
+                     the full list of properties of the message.
+        :param exact: if True, require exact match with key. Otherwise
+                      treat as prefix.
+        :yields:  Each property values as a pair of `name, value`
+        :ytype:   pairs of str
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        properties = Message._get_properties(self._msg, _str(prop), exact)
+        while Message._properties_valid(properties):
+            key = Message._properties_key(properties)
+            value = Message._properties_value(properties)
+            yield key.decode("utf-8"), value.decode("utf-8")
+            Message._properties_move_to_next(properties)
+
+    def freeze(self):
+        """Freezes the current state of 'message' within the database
+
+        This means that changes to the message state, (via :meth:`add_tag`,
+        :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
+        committed to the database until the message is :meth:`thaw` ed.
+
+        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::
+
+          msg.freeze()
+          msg.remove_all_tags(False)
+          for tag in new_tags:
+              msg.add_tag(tag, False)
+          msg.thaw()
+          msg.tags_to_maildir_flags()
+
+        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
+        :meth:`remove_all_tags` but before :meth:`add_tag`.
+
+        :returns: STATUS.SUCCESS if the message was successfully frozen.
+                  Raises an exception otherwise.
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._freeze(self._msg)
+
+        if STATUS.SUCCESS == status:
+            # return on success
+            return status
+
+        raise NotmuchError(status)
+
+    _thaw = nmlib.notmuch_message_thaw
+    _thaw.argtypes = [NotmuchMessageP]
+    _thaw.restype = c_uint
+
+    def thaw(self):
+        """Thaws the current 'message'
+
+        Thaw the current 'message', synchronizing any changes that may have
+        occurred while 'message' was frozen into the notmuch database.
+
+        See :meth:`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.
+
+        :returns: STATUS.SUCCESS if the message was successfully frozen.
+                  Raises an exception otherwise.
+        :raises: :exc:`UnbalancedFreezeThawError` if an attempt was made
+                 to thaw an unfrozen message. That is, there have been
+                 an unbalanced number of calls to :meth:`freeze` and
+                 :meth:`thaw`.
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._thaw(self._msg)
+
+        if STATUS.SUCCESS == status:
+            # return on success
+            return status
+
+        raise NotmuchError(status)
+
+    def is_match(self):
+        """(Not implemented)"""
+        return self.get_flag(Message.FLAG.MATCH)
+
+    def tags_to_maildir_flags(self):
+        """Synchronize notmuch tags to file Maildir flags
+
+              'D' if the message has the "draft" tag
+              'F' if the message has the "flagged" tag
+              'P' if the message has the "passed" tag
+              'R' if the message has the "replied" tag
+              'S' if the message does not have the "unread" tag
+
+        Any existing flags unmentioned in the list above will be
+        preserved in the renaming.
+
+        Also, if this filename is in a directory named "new", rename it
+        to be within the neighboring directory named "cur".
+
+        Do note that calling this method while a message is frozen might
+        not work yet, as the modified tags have not been committed yet
+        to the database.
+
+        :returns: a :class:`STATUS` value. In short, you want to see
+            notmuch.STATUS.SUCCESS here. See there for details."""
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._tags_to_maildir_flags(self._msg)
+
+    def maildir_flags_to_tags(self):
+        """Synchronize file Maildir flags to notmuch tags
+
+            Flag    Action if present
+            ----    -----------------
+            'D'     Adds the "draft" tag to the message
+            'F'     Adds the "flagged" tag to the message
+            'P'     Adds the "passed" tag to the message
+            'R'     Adds the "replied" tag to the message
+            'S'     Removes the "unread" tag from the message
+
+        For each flag that is not present, the opposite action
+        (add/remove) is performed for the corresponding tags.  If there
+        are multiple filenames associated with this message, the flag is
+        considered present if it appears in one or more filenames. (That
+        is, the flags from the multiple filenames are combined with the
+        logical OR operator.)
+
+        As a convenience, you can set the sync_maildir_flags parameter in
+        :meth:`Database.index_file` to implicitly call this.
+
+        :returns: a :class:`STATUS`. In short, you want to see
+            notmuch.STATUS.SUCCESS here. See there for details."""
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._maildir_flags_to_tags(self._msg)
+
+    def __repr__(self):
+        """Represent a Message() object by str()"""
+        return self.__str__()
+
+    def __unicode__(self):
+        format = "%s (%s) (%s)"
+        return format % (self.get_header('from'),
+                         self.get_tags(),
+                         date.fromtimestamp(self.get_date()),
+                        )
+
+    def get_message_parts(self):
+        """Output like notmuch show"""
+        fp = open(self.get_filename(), 'rb')
+        if sys.version_info[0] < 3:
+            email_msg = email.message_from_file(fp)
+        else:
+            email_msg = email.message_from_binary_file(fp)
+        fp.close()
+
+        out = []
+        for msg in email_msg.walk():
+            if not msg.is_multipart():
+                out.append(msg)
+        return out
+
+    def get_part(self, num):
+        """Returns the nth message body part"""
+        parts = self.get_message_parts()
+        if (num <= 0 or num > len(parts)):
+            return ""
+        else:
+            out_part = parts[(num - 1)]
+            return out_part.get_payload(decode=True)
+
+    def __hash__(self):
+        """Implement hash(), so we can use Message() sets"""
+        file = self.get_filename()
+        if not file:
+            return None
+        return hash(file)
+
+    def __cmp__(self, other):
+        """Implement cmp(), so we can compare Message()s
+
+        2 messages are considered equal if they point to the same
+        Message-Id and if they point to the same file names. If 2
+        Messages derive from different queries where some files have
+        been added or removed, the same messages would not be considered
+        equal (as they do not point to the same set of files
+        any more)."""
+        res = cmp(self.get_message_id(), other.get_message_id())
+        if res:
+            res = cmp(list(self.get_filenames()), list(other.get_filenames()))
+        return res
+
+    _destroy = nmlib.notmuch_message_destroy
+    _destroy.argtypes = [NotmuchMessageP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Message"""
+        if self._msg:
+            self._destroy(self._msg)
diff --git a/contrib/python-legacy/notmuch/messages.py b/contrib/python-legacy/notmuch/messages.py
new file mode 100644 (file)
index 0000000..3801c66
--- /dev/null
@@ -0,0 +1,199 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+               Jesse Rosenthal <jrosenthal@jhu.edu>
+"""
+
+from .globals import (
+    nmlib,
+    NotmuchTagsP,
+    NotmuchMessageP,
+    NotmuchMessagesP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+from .tag import Tags
+from .message import Message
+
+class Messages(object):
+    r"""Represents a list of notmuch messages
+
+    This object provides an iterator over a list of notmuch messages
+    (Technically, it provides a wrapper for the underlying
+    *notmuch_messages_t* structure). Do note that the underlying library
+    only provides a one-time iterator (it cannot reset the iterator to
+    the start). Thus iterating over the function will "exhaust" the list
+    of messages, and a subsequent iteration attempt will raise a
+    :exc:`NotInitializedError`. If you need to
+    re-iterate over a list of messages you will need to retrieve a new
+    :class:`Messages` object or cache your :class:`Message`\s in a list
+    via::
+
+       msglist = list(msgs)
+
+    You can store and reuse the single :class:`Message` objects as often
+    as you want as long as you keep the parent :class:`Messages` object
+    around. (Due to hierarchical memory allocation, all derived
+    :class:`Message` objects will be invalid when we delete the parent
+    :class:`Messages` object, even if it was already exhausted.) So
+    this works::
+
+      db   = Database()
+      msgs = Query(db,'').search_messages() #get a Messages() object
+      msglist = list(msgs)
+
+      # msgs is "exhausted" now and msgs.next() will raise an exception.
+      # However it will be kept alive until all retrieved Message()
+      # objects are also deleted. If you do e.g. an explicit del(msgs)
+      # here, the following lines would fail.
+
+      # You can reiterate over *msglist* however as often as you want.
+      # It is simply a list with :class:`Message`s.
+
+      print (msglist[0].get_filename())
+      print (msglist[1].get_filename())
+      print (msglist[0].get_message_id())
+
+
+    As :class:`Message` implements both __hash__() and __cmp__(), it is
+    possible to make sets out of :class:`Messages` and use set
+    arithmetic (this happens in python and will of course be *much*
+    slower than redoing a proper query with the appropriate filters::
+
+        s1, s2 = set(msgs1), set(msgs2)
+        s.union(s2)
+        s1 -= s2
+        ...
+
+    Be careful when using set arithmetic between message sets derived
+    from different Databases (ie the same database reopened after
+    messages have changed). If messages have added or removed associated
+    files in the meantime, it is possible that the same message would be
+    considered as a different object (as it points to a different file).
+    """
+
+    #notmuch_messages_get
+    _get = nmlib.notmuch_messages_get
+    _get.argtypes = [NotmuchMessagesP]
+    _get.restype = NotmuchMessageP
+
+    _collect_tags = nmlib.notmuch_messages_collect_tags
+    _collect_tags.argtypes = [NotmuchMessagesP]
+    _collect_tags.restype = NotmuchTagsP
+
+    def __init__(self, msgs_p, parent=None):
+        """
+        :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Messages` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type msgs_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object
+             (ie :class:`Query`) these tags are derived from. It saves
+             a reference to it, so we can automatically delete the db
+             object once all derived objects are dead.
+        :TODO: Make the iterator work more than once and cache the tags in
+               the Python object.(?)
+        """
+        if not msgs_p:
+            raise NullPointerError()
+
+        self._msgs = msgs_p
+        #store parent, so we keep them alive as long as self  is alive
+        self._parent = parent
+
+    def collect_tags(self):
+        """Return the unique :class:`Tags` in the contained messages
+
+        :returns: :class:`Tags`
+        :exceptions: :exc:`NotInitializedError` if not init'ed
+
+        .. note::
+
+            :meth:`collect_tags` will iterate over the messages and therefore
+            will not allow further iterations.
+        """
+        if not self._msgs:
+            raise NotInitializedError()
+
+        # collect all tags (returns NULL on error)
+        tags_p = Messages._collect_tags(self._msgs)
+        #reset _msgs as we iterated over it and can do so only once
+        self._msgs = None
+
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def __iter__(self):
+        """ Make Messages an iterator """
+        return self
+
+    _valid = nmlib.notmuch_messages_valid
+    _valid.argtypes = [NotmuchMessagesP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_messages_move_to_next
+    _move_to_next.argtypes = [NotmuchMessagesP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._msgs:
+            raise NotInitializedError()
+
+        if not self._valid(self._msgs):
+            self._msgs = None
+            raise StopIteration
+
+        msg = Message(Messages._get(self._msgs), self)
+        self._move_to_next(self._msgs)
+        return msg
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __nonzero__(self):
+        '''
+        Implement truth value testing. If __nonzero__ is not
+        implemented, the python runtime would fall back to `len(..) >
+        0` thus exhausting the iterator.
+
+        :returns: True if the wrapped iterator has at least one more object
+                  left.
+        '''
+        return self._msgs and self._valid(self._msgs)
+
+    _destroy = nmlib.notmuch_messages_destroy
+    _destroy.argtypes = [NotmuchMessagesP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Messages"""
+        if self._msgs:
+            self._destroy(self._msgs)
+
+class EmptyMessagesResult(Messages):
+    def __init__(self, parent):
+        self._msgs = None
+        self._parent = parent
+
+    def __next__(self):
+        raise StopIteration()
+    next = __next__
diff --git a/contrib/python-legacy/notmuch/query.py b/contrib/python-legacy/notmuch/query.py
new file mode 100644 (file)
index 0000000..ffb86df
--- /dev/null
@@ -0,0 +1,237 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_char_p, c_uint, POINTER, byref
+from .globals import (
+    nmlib,
+    Enum,
+    _str,
+    NotmuchQueryP,
+    NotmuchThreadsP,
+    NotmuchDatabaseP,
+    NotmuchMessagesP,
+)
+from .errors import (
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+)
+from .threads import Threads
+from .messages import Messages
+
+
+class Query(object):
+    """Represents a search query on an opened :class:`Database`.
+
+    A query selects and filters a subset of messages from the notmuch
+    database we derive from.
+
+    :class:`Query` provides an instance attribute :attr:`sort`, which
+    contains the sort order (if specified via :meth:`set_sort`) or
+    `None`.
+
+    Any function in this class may throw an :exc:`NotInitializedError`
+    in case the underlying query object was not set up correctly.
+
+    .. note:: Do remember that as soon as we tear down this object,
+           all underlying derived objects such as threads,
+           messages, tags etc will be freed by the underlying library
+           as well. Accessing these objects will lead to segfaults and
+           other unexpected behavior. See above for more details.
+    """
+    # constants
+    SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
+    """Constants: Sort order in which to return results"""
+
+    def __init__(self, db, querystr):
+        """
+        :param db: An open database which we derive the Query from.
+        :type db: :class:`Database`
+        :param querystr: The query string for the message.
+        :type querystr: utf-8 encoded str or unicode
+        """
+        self._db = None
+        self._query = None
+        self.sort = None
+        self.create(db, querystr)
+
+    def _assert_query_is_initialized(self):
+        """Raises :exc:`NotInitializedError` if self._query is `None`"""
+        if not self._query:
+            raise NotInitializedError()
+
+    """notmuch_query_create"""
+    _create = nmlib.notmuch_query_create
+    _create.argtypes = [NotmuchDatabaseP, c_char_p]
+    _create.restype = NotmuchQueryP
+
+    def create(self, db, querystr):
+        """Creates a new query derived from a Database
+
+        This function is utilized by __init__() and usually does not need to
+        be called directly.
+
+        :param db: Database to create the query from.
+        :type db: :class:`Database`
+        :param querystr: The query string
+        :type querystr: utf-8 encoded str or unicode
+        :raises:
+            :exc:`NullPointerError` if the query creation failed
+                (e.g. too little memory).
+            :exc:`NotInitializedError` if the underlying db was not
+                initialized.
+        """
+        db._assert_db_is_initialized()
+        # create reference to parent db to keep it alive
+        self._db = db
+        # create query, return None if too little mem available
+        query_p = Query._create(db._db, _str(querystr))
+        if not query_p:
+            raise NullPointerError
+        self._query = query_p
+
+    _set_sort = nmlib.notmuch_query_set_sort
+    _set_sort.argtypes = [NotmuchQueryP, c_uint]
+    _set_sort.restype = None
+
+    def set_sort(self, sort):
+        """Set the sort order future results will be delivered in
+
+        :param sort: Sort order (see :attr:`Query.SORT`)
+        """
+        self._assert_query_is_initialized()
+        self.sort = sort
+        self._set_sort(self._query, sort)
+
+    _exclude_tag = nmlib.notmuch_query_add_tag_exclude
+    _exclude_tag.argtypes = [NotmuchQueryP, c_char_p]
+    _exclude_tag.restype = None
+
+    def exclude_tag(self, tagname):
+        """Add a tag that will be excluded from the query results by default.
+
+        This exclusion will be overridden if this tag appears explicitly in the
+        query.
+
+        :param tagname: Name of the tag to be excluded
+        """
+        self._assert_query_is_initialized()
+        self._exclude_tag(self._query, _str(tagname))
+
+    """notmuch_query_search_threads"""
+    _search_threads = nmlib.notmuch_query_search_threads
+    _search_threads.argtypes = [NotmuchQueryP, POINTER(NotmuchThreadsP)]
+    _search_threads.restype = c_uint
+
+    def search_threads(self):
+        r"""Execute a query for threads
+
+        Execute a query for threads, returning a :class:`Threads` iterator.
+        The returned threads are owned by the query and as such, will only be
+        valid until the Query is deleted.
+
+        The method sets :attr:`Message.FLAG`\.MATCH for those messages that
+        match the query. The method :meth:`Message.get_flag` allows us
+        to get the value of this flag.
+
+        :returns: :class:`Threads`
+        :raises: :exc:`NullPointerError` if search_threads failed
+        """
+        self._assert_query_is_initialized()
+        threads_p = NotmuchThreadsP() # == NULL
+        status = Query._search_threads(self._query, byref(threads_p))
+        if status != 0:
+            raise NotmuchError(status)
+
+        if not threads_p:
+            raise NullPointerError
+        return Threads(threads_p, self)
+
+    """notmuch_query_search_messages_st"""
+    _search_messages = nmlib.notmuch_query_search_messages
+    _search_messages.argtypes = [NotmuchQueryP, POINTER(NotmuchMessagesP)]
+    _search_messages.restype = c_uint
+
+    def search_messages(self):
+        """Filter messages according to the query and return
+        :class:`Messages` in the defined sort order
+
+        :returns: :class:`Messages`
+        :raises: :exc:`NullPointerError` if search_messages failed
+        """
+        self._assert_query_is_initialized()
+        msgs_p = NotmuchMessagesP() # == NULL
+        status = Query._search_messages(self._query, byref(msgs_p))
+        if status != 0:
+            raise NotmuchError(status)
+
+        if not msgs_p:
+            raise NullPointerError
+        return Messages(msgs_p, self)
+
+    _count_messages = nmlib.notmuch_query_count_messages
+    _count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
+    _count_messages.restype = c_uint
+
+    def count_messages(self):
+        '''
+        This function performs a search and returns Xapian's best
+        guess as to the number of matching messages.
+
+        :returns: the estimated number of messages matching this query
+        :rtype:   int
+        '''
+        self._assert_query_is_initialized()
+        count = c_uint(0)
+        status = Query._count_messages(self._query, byref(count))
+        if status != 0:
+            raise NotmuchError(status)
+        return count.value
+
+    _count_threads = nmlib.notmuch_query_count_threads
+    _count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
+    _count_threads.restype = c_uint
+
+    def count_threads(self):
+        '''
+        This function performs a search and returns the number of
+        unique thread IDs in the matching messages. This is the same
+        as number of threads matching a search.
+
+        Note that this is a significantly heavier operation than
+        meth:`Query.count_messages`.
+
+        :returns: the number of threads returned by this query
+        :rtype:   int
+        '''
+        self._assert_query_is_initialized()
+        count = c_uint(0)
+        status = Query._count_threads(self._query, byref(count))
+        if status != 0:
+            raise NotmuchError(status)
+        return count.value
+
+    _destroy = nmlib.notmuch_query_destroy
+    _destroy.argtypes = [NotmuchQueryP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the Query"""
+        if self._query:
+            self._destroy(self._query)
diff --git a/contrib/python-legacy/notmuch/tag.py b/contrib/python-legacy/notmuch/tag.py
new file mode 100644 (file)
index 0000000..fbb18ce
--- /dev/null
@@ -0,0 +1,141 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+from ctypes import c_char_p
+from .globals import (
+    nmlib,
+    Python3StringMixIn,
+    NotmuchTagsP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+
+
+class Tags(Python3StringMixIn):
+    """Represents a list of notmuch tags
+
+    This object provides an iterator over a list of notmuch tags (which
+    are unicode instances).
+
+    Do note that the underlying library only provides a one-time
+    iterator (it cannot reset the iterator to the start). Thus iterating
+    over the function will "exhaust" the list of tags, and a subsequent
+    iteration attempt will raise a :exc:`NotInitializedError`.
+    Also note, that any function that uses iteration (nearly all) will
+    also exhaust the tags. So both::
+
+      for tag in tags: print tag
+
+    as well as::
+
+       number_of_tags = len(tags)
+
+    and even a simple::
+
+       #str() iterates over all tags to construct a space separated list
+       print(str(tags))
+
+    will "exhaust" the Tags. If you need to re-iterate over a list of
+    tags you will need to retrieve a new :class:`Tags` object.
+    """
+
+    #notmuch_tags_get
+    _get = nmlib.notmuch_tags_get
+    _get.argtypes = [NotmuchTagsP]
+    _get.restype = c_char_p
+
+    def __init__(self, tags_p, parent=None):
+        """
+        :param tags_p: A pointer to an underlying *notmuch_tags_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Tags` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type tags_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object (ie :class:`Database` or
+             :class:`Message` these tags are derived from, and saves a
+             reference to it, so we can automatically delete the db object
+             once all derived objects are dead.
+        :TODO: Make the iterator optionally work more than once by
+               cache the tags in the Python object(?)
+        """
+        if not tags_p:
+            raise NullPointerError()
+
+        self._tags = tags_p
+        #save reference to parent object so we keep it alive
+        self._parent = parent
+
+    def __iter__(self):
+        """ Make Tags an iterator """
+        return self
+
+    _valid = nmlib.notmuch_tags_valid
+    _valid.argtypes = [NotmuchTagsP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_tags_move_to_next
+    _move_to_next.argtypes = [NotmuchTagsP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._tags:
+            raise NotInitializedError()
+        if not self._valid(self._tags):
+            self._tags = None
+            raise StopIteration
+        tag = Tags._get(self._tags).decode('UTF-8')
+        self._move_to_next(self._tags)
+        return tag
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __nonzero__(self):
+        '''
+        Implement truth value testing. If __nonzero__ is not
+        implemented, the python runtime would fall back to `len(..) >
+        0` thus exhausting the iterator.
+
+        :returns: True if the wrapped iterator has at least one more object
+                  left.
+        '''
+        return self._tags and self._valid(self._tags)
+
+    def __unicode__(self):
+        """string representation of :class:`Tags`: a space separated list of tags
+
+        .. note::
+
+            As this iterates over the tags, we will not be able to iterate over
+            them again (as in retrieve them)! If the tags have been exhausted
+            already, this will raise a :exc:`NotInitializedError`on subsequent
+            attempts.
+        """
+        return " ".join(self)
+
+    _destroy = nmlib.notmuch_tags_destroy
+    _destroy.argtypes = [NotmuchTagsP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch tags"""
+        if self._tags:
+            self._destroy(self._tags)
diff --git a/contrib/python-legacy/notmuch/thread.py b/contrib/python-legacy/notmuch/thread.py
new file mode 100644 (file)
index 0000000..9aa0e08
--- /dev/null
@@ -0,0 +1,281 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_char_p, c_long, c_int
+from .globals import (
+    nmlib,
+    NotmuchThreadP,
+    NotmuchMessagesP,
+    NotmuchTagsP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+from .messages import Messages
+from .tag import Tags
+from datetime import date
+
+class Thread(object):
+    """Represents a single message thread."""
+
+    """notmuch_thread_get_thread_id"""
+    _get_thread_id = nmlib.notmuch_thread_get_thread_id
+    _get_thread_id.argtypes = [NotmuchThreadP]
+    _get_thread_id.restype = c_char_p
+
+    """notmuch_thread_get_authors"""
+    _get_authors = nmlib.notmuch_thread_get_authors
+    _get_authors.argtypes = [NotmuchThreadP]
+    _get_authors.restype = c_char_p
+
+    """notmuch_thread_get_subject"""
+    _get_subject = nmlib.notmuch_thread_get_subject
+    _get_subject.argtypes = [NotmuchThreadP]
+    _get_subject.restype = c_char_p
+
+    """notmuch_thread_get_toplevel_messages"""
+    _get_toplevel_messages = nmlib.notmuch_thread_get_toplevel_messages
+    _get_toplevel_messages.argtypes = [NotmuchThreadP]
+    _get_toplevel_messages.restype = NotmuchMessagesP
+
+    _get_newest_date = nmlib.notmuch_thread_get_newest_date
+    _get_newest_date.argtypes = [NotmuchThreadP]
+    _get_newest_date.restype = c_long
+
+    _get_oldest_date = nmlib.notmuch_thread_get_oldest_date
+    _get_oldest_date.argtypes = [NotmuchThreadP]
+    _get_oldest_date.restype = c_long
+
+    """notmuch_thread_get_tags"""
+    _get_tags = nmlib.notmuch_thread_get_tags
+    _get_tags.argtypes = [NotmuchThreadP]
+    _get_tags.restype = NotmuchTagsP
+
+    def __init__(self, thread_p, parent=None):
+        """
+        :param thread_p: A pointer to an internal notmuch_thread_t
+            Structure.  These are not publicly exposed, so a user
+            will almost never instantiate a :class:`Thread` object
+            herself. They are usually handed back as a result,
+            e.g. when iterating through :class:`Threads`. *thread_p*
+            must be valid, we will raise an :exc:`NullPointerError`
+            if it is `None`.
+
+        :param parent: A 'parent' object is passed which this message is
+              derived from. We save a reference to it, so we can
+              automatically delete the parent object once all derived
+              objects are dead.
+        """
+        if not thread_p:
+            raise NullPointerError()
+        self._thread = thread_p
+        #keep reference to parent, so we keep it alive
+        self._parent = parent
+
+    def get_thread_id(self):
+        """Get the thread ID of 'thread'
+
+        The returned string belongs to 'thread' and will only be valid
+        for as long as the thread is valid.
+
+        :returns: String with a message ID
+        :raises: :exc:`NotInitializedError` if the thread
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore')
+
+    _get_total_messages = nmlib.notmuch_thread_get_total_messages
+    _get_total_messages.argtypes = [NotmuchThreadP]
+    _get_total_messages.restype = c_int
+
+    def get_total_messages(self):
+        """Get the total number of messages in 'thread'
+
+        :returns: The number of all messages in the database
+                  belonging to this thread. Contrast with
+                  :meth:`get_matched_messages`.
+        :raises: :exc:`NotInitializedError` if the thread
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return self._get_total_messages(self._thread)
+
+    def get_toplevel_messages(self):
+        """Returns a :class:`Messages` iterator for the top-level messages in
+           'thread'
+
+           This iterator will not necessarily iterate over all of the messages
+           in the thread. It will only iterate over the messages in the thread
+           which are not replies to other messages in the thread.
+
+        :returns: :class:`Messages`
+        :raises: :exc:`NotInitializedError` if query is not initialized
+        :raises: :exc:`NullPointerError` if search_messages failed
+        """
+        if not self._thread:
+            raise NotInitializedError()
+
+        msgs_p = Thread._get_toplevel_messages(self._thread)
+
+        if not msgs_p:
+            raise NullPointerError()
+
+        return Messages(msgs_p, self)
+
+    """notmuch_thread_get_messages"""
+    _get_messages = nmlib.notmuch_thread_get_messages
+    _get_messages.argtypes = [NotmuchThreadP]
+    _get_messages.restype = NotmuchMessagesP
+
+    def get_messages(self):
+        """Returns a :class:`Messages` iterator for all messages in 'thread'
+
+        :returns: :class:`Messages`
+        :raises: :exc:`NotInitializedError` if query is not initialized
+        :raises: :exc:`NullPointerError` if get_messages failed
+        """
+        if not self._thread:
+            raise NotInitializedError()
+
+        msgs_p = Thread._get_messages(self._thread)
+
+        if not msgs_p:
+            raise NullPointerError()
+
+        return Messages(msgs_p, self)
+
+    _get_matched_messages = nmlib.notmuch_thread_get_matched_messages
+    _get_matched_messages.argtypes = [NotmuchThreadP]
+    _get_matched_messages.restype = c_int
+
+    def get_matched_messages(self):
+        """Returns the number of messages in 'thread' that matched the query
+
+        :returns: The number of all messages belonging to this thread that
+                  matched the :class:`Query`from which this thread was created.
+                  Contrast with :meth:`get_total_messages`.
+        :raises: :exc:`NotInitializedError` if the thread
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return self._get_matched_messages(self._thread)
+
+    def get_authors(self):
+        """Returns the authors of 'thread'
+
+        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 returned string belongs to 'thread' and will only be valid for
+        as long as this Thread() is not deleted.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        authors = Thread._get_authors(self._thread)
+        if not authors:
+            return None
+        return authors.decode('UTF-8', 'ignore')
+
+    def get_subject(self):
+        """Returns the Subject of 'thread'
+
+        The returned string belongs to 'thread' and will only be valid for
+        as long as this Thread() is not deleted.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        subject = Thread._get_subject(self._thread)
+        if not subject:
+            return None
+        return subject.decode('UTF-8', 'ignore')
+
+    def get_newest_date(self):
+        """Returns time_t of the newest message date
+
+        :returns: A time_t timestamp.
+        :rtype: c_unit64
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return Thread._get_newest_date(self._thread)
+
+    def get_oldest_date(self):
+        """Returns time_t of the oldest message date
+
+        :returns: A time_t timestamp.
+        :rtype: c_unit64
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return Thread._get_oldest_date(self._thread)
+
+    def get_tags(self):
+        """ Returns the message tags
+
+        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 :class:`Tags` object is owned by the thread and as such, will only
+        be valid for as long as this :class:`Thread` is valid (e.g. until the
+        query from which it derived is explicitly deleted).
+
+        :returns: A :class:`Tags` iterator.
+        :raises: :exc:`NotInitializedError` if query is not initialized
+        :raises: :exc:`NullPointerError` if search_messages failed
+        """
+        if not self._thread:
+            raise NotInitializedError()
+
+        tags_p = Thread._get_tags(self._thread)
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def __unicode__(self):
+        frm = "thread:%s %12s [%d/%d] %s; %s (%s)"
+
+        return frm % (self.get_thread_id(),
+                      date.fromtimestamp(self.get_newest_date()),
+                      self.get_matched_messages(),
+                      self.get_total_messages(),
+                      self.get_authors(),
+                      self.get_subject(),
+                      self.get_tags(),
+                     )
+
+    _destroy = nmlib.notmuch_thread_destroy
+    _destroy.argtypes = [NotmuchThreadP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Thread"""
+        if self._thread:
+            self._destroy(self._thread)
diff --git a/contrib/python-legacy/notmuch/threads.py b/contrib/python-legacy/notmuch/threads.py
new file mode 100644 (file)
index 0000000..0c382d5
--- /dev/null
@@ -0,0 +1,152 @@
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from .globals import (
+    nmlib,
+    Python3StringMixIn,
+    NotmuchThreadP,
+    NotmuchThreadsP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+from .thread import Thread
+
+class Threads(Python3StringMixIn):
+    """Represents a list of notmuch threads
+
+    This object provides an iterator over a list of notmuch threads
+    (Technically, it provides a wrapper for the underlying
+    *notmuch_threads_t* structure). Do note that the underlying
+    library only provides a one-time iterator (it cannot reset the
+    iterator to the start). Thus iterating over the function will
+    "exhaust" the list of threads, and a subsequent iteration attempt
+    will raise a :exc:`NotInitializedError`. Also
+    note, that any function that uses iteration will also
+    exhaust the messages. So both::
+
+      for thread in threads: print thread
+
+    as well as::
+
+       list_of_threads = list(threads)
+
+    will "exhaust" the threads. If you need to re-iterate over a list of
+    messages you will need to retrieve a new :class:`Threads` object.
+
+    Things are not as bad as it seems though, you can store and reuse
+    the single Thread objects as often as you want as long as you
+    keep the parent Threads object around. (Recall that due to
+    hierarchical memory allocation, all derived Threads objects will
+    be invalid when we delete the parent Threads() object, even if it
+    was already "exhausted".) So this works::
+
+      db   = Database()
+      threads = Query(db,'').search_threads() #get a Threads() object
+      threadlist = []
+      for thread in threads:
+         threadlist.append(thread)
+
+      # threads is "exhausted" now.
+      # However it will be kept around until all retrieved Thread() objects are
+      # also deleted. If you did e.g. an explicit del(threads) here, the
+      # following lines would fail.
+
+      # You can reiterate over *threadlist* however as often as you want.
+      # It is simply a list with Thread objects.
+
+      print (threadlist[0].get_thread_id())
+      print (threadlist[1].get_thread_id())
+      print (threadlist[0].get_total_messages())
+    """
+
+    #notmuch_threads_get
+    _get = nmlib.notmuch_threads_get
+    _get.argtypes = [NotmuchThreadsP]
+    _get.restype = NotmuchThreadP
+
+    def __init__(self, threads_p, parent=None):
+        """
+        :param threads_p:  A pointer to an underlying *notmuch_threads_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Threads` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Query.search_threads`.  *threads_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type threads_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object
+             (ie :class:`Query`) these tags are derived from. It saves
+             a reference to it, so we can automatically delete the db
+             object once all derived objects are dead.
+        :TODO: Make the iterator work more than once and cache the tags in
+               the Python object.(?)
+        """
+        if not threads_p:
+            raise NullPointerError()
+
+        self._threads = threads_p
+        #store parent, so we keep them alive as long as self  is alive
+        self._parent = parent
+
+    def __iter__(self):
+        """ Make Threads an iterator """
+        return self
+
+    _valid = nmlib.notmuch_threads_valid
+    _valid.argtypes = [NotmuchThreadsP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_threads_move_to_next
+    _move_to_next.argtypes = [NotmuchThreadsP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._threads:
+            raise NotInitializedError()
+
+        if not self._valid(self._threads):
+            self._threads = None
+            raise StopIteration
+
+        thread = Thread(Threads._get(self._threads), self)
+        self._move_to_next(self._threads)
+        return thread
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __nonzero__(self):
+        '''
+        Implement truth value testing. If __nonzero__ is not
+        implemented, the python runtime would fall back to `len(..) >
+        0` thus exhausting the iterator.
+
+        :returns: True if the wrapped iterator has at least one more object
+                  left.
+        '''
+        return self._threads and self._valid(self._threads)
+
+    _destroy = nmlib.notmuch_threads_destroy
+    _destroy.argtypes = [NotmuchThreadsP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Threads"""
+        if self._threads:
+            self._destroy(self._threads)
diff --git a/contrib/python-legacy/notmuch/version.py b/contrib/python-legacy/notmuch/version.py
new file mode 100644 (file)
index 0000000..fd4152e
--- /dev/null
@@ -0,0 +1,3 @@
+# this file should be kept in sync with ../../../version
+__VERSION__ = '0.38.3'
+SOVERSION = '5'
diff --git a/contrib/python-legacy/setup.py b/contrib/python-legacy/setup.py
new file mode 100644 (file)
index 0000000..6308b9f
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+
+"""
+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 <https://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+import os
+from distutils.core import setup
+
+# get the notmuch version number without importing the notmuch module
+version_file = os.path.join(os.path.dirname(__file__),
+                            'notmuch', 'version.py')
+exec(compile(open(version_file).read(), version_file, 'exec'))
+assert '__VERSION__' in globals(), \
+    'Failed to read the notmuch binding version number'
+
+setup(name='notmuch',
+      version=__VERSION__,
+      description='Python binding of the notmuch mail search and indexing library.',
+      author='Sebastian Spaeth',
+      author_email='Sebastian@SSpaeth.de',
+      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
+<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 <https://notmuch.readthedocs.io/>`_.
+
+Requirements
+------------
+
+You need to have notmuch installed (or rather libnotmuch.so.1). Also,
+notmuch makes use of the ctypes library, and has only been tested with
+python >= 2.5. It will not work on earlier python versions.
+''',
+      classifiers=['Development Status :: 3 - Alpha',
+                   'Intended Audience :: Developers',
+                   'License :: OSI Approved :: GNU General Public License (GPL)',
+                   'Programming Language :: Python :: 2',
+                   'Programming Language :: Python :: 3',
+                   'Topic :: Communications :: Email',
+                   'Topic :: Software Development :: Libraries'
+                   ],
+      platforms='',
+      license='https://www.gnu.org/licenses/gpl-3.0.txt',
+     )
index 846ccd2eab087523435d471aaa36557f907a055f..ef8351660cde90e6a4b9d132fc7cc4191e3afbd8 100644 (file)
@@ -1,3 +1,168 @@
+notmuch (0.39-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Legacy python bindings (package python3-notmuch) are no longer
+    built. Consider switching to python3-notmuch2.
+
+ -- David Bremner <bremner@debian.org>  Sat, 15 Mar 2025 07:03:50 -0300
+
+notmuch (0.39~rc3-1) experimental; urgency=medium
+
+  * New upstream pre-release
+
+ -- David Bremner <bremner@debian.org>  Fri, 14 Mar 2025 19:07:04 -0300
+
+notmuch (0.39~rc2-2) experimental; urgency=medium
+
+  * Increase test timeout to 10m (per test script). This is currently
+    only needed for riscv64, but should be harmless elsewhere.
+
+ -- David Bremner <bremner@debian.org>  Tue, 11 Mar 2025 19:39:27 -0300
+
+notmuch (0.39~rc2-1) experimental; urgency=medium
+
+  * New upstream pre-release
+  * Bug fix: "FTBFS: 1 test failed.", thanks to Santiago Vila (Closes:
+    #1099252).
+
+ -- David Bremner <bremner@debian.org>  Mon, 10 Mar 2025 21:00:09 -0300
+
+notmuch (0.39~rc1-1) experimental; urgency=medium
+
+  * New upstream pre-release
+  * Bug fix: "FTBFS: 3 tests fail against Emacs 30.1", thanks to Sean
+    Whitton (Closes: #1098852). Force emacs 30 to use the older
+    pretty-printing style.
+
+ -- David Bremner <bremner@debian.org>  Tue, 25 Feb 2025 16:35:06 -0400
+
+notmuch (0.39~rc0-1) experimental; urgency=medium
+
+  * New upstream pre-release
+  * Remove binary package python3-notmuch (Closes: #893057, #970834, #749890).
+  * Bug fix: "saving draft causing missing header fields", thanks to
+    Xiyue Deng (Closes: #1092047).
+
+ -- David Bremner <bremner@debian.org>  Sat, 22 Feb 2025 08:12:33 -0400
+
+notmuch (0.38.3-5) unstable; urgency=medium
+
+  * Bug fix: "test suite regressions with fixed GnuPG", thanks to
+    Daniel Kahn Gillmor (Closes: #1098995).
+
+ -- David Bremner <bremner@debian.org>  Fri, 28 Feb 2025 16:24:35 -0500
+
+notmuch (0.38.3-4) unstable; urgency=medium
+
+  * Bug fix: "FTBFS: 3 tests fail against Emacs 30.1", thanks to Sean
+    Whitton (Closes: #1098852). Cherry-pick upstream commit e3d4721b1.
+
+ -- David Bremner <bremner@debian.org>  Tue, 25 Feb 2025 16:51:36 -0400
+
+notmuch (0.38.3-3) unstable; urgency=medium
+
+  [ James McCoy ]
+  * Convert notmuch-vim to Vim addon policy 2.0 (Closes: #1015936).
+
+  [ Nicholas D Steeves ]
+  * Allow neomutt to fulfill the "mutt" requirement of notmuch-mutt, and add
+    related Enhances (Closes: #1029190).
+  * Add Astroid as an alternative Recommends; this one is a GUI client that
+    may be less intimidating for new users.
+
+  [ David Bremner ]
+  * remove "Build-Conflicts: gdb-minimal", thanks to Jeremy Bícha (Closes:
+    #1077911).
+  * skip test T810-tsan everywhere, as it seems broken by the latest glib
+    (Closes: #1077910). Thanks to Jeremy Bícha for the report
+  * Replace build-dependency on pkg-config with pkgconf
+
+ -- David Bremner <bremner@debian.org>  Mon, 05 Aug 2024 10:13:01 -0300
+
+notmuch (0.38.3-2) unstable; urgency=medium
+
+  * No change upload to rebuild with dh-elpa 2.1.5
+
+ -- David Bremner <bremner@debian.org>  Fri, 26 Jul 2024 11:59:41 +0900
+
+notmuch (0.38.3-1) unstable; urgency=medium
+
+  * New upstream bugfix release
+  * Bug fix: "Recommends transitional package gnupg-agent instead of
+    gpg-agent", thanks to Andreas Metzler (Closes: #1064114).
+
+ -- David Bremner <bremner@debian.org>  Sat, 09 Mar 2024 23:13:07 -0400
+
+notmuch (0.38.2-1.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Rename libraries for 64-bit time_t transition.  Closes: #1063205
+
+ -- Benjamin Drung <bdrung@debian.org>  Wed, 28 Feb 2024 23:56:48 +0000
+
+notmuch (0.38.2-1) unstable; urgency=medium
+
+  * New upstream bugfix release
+
+ -- David Bremner <bremner@debian.org>  Fri, 01 Dec 2023 07:51:09 -0400
+
+notmuch (0.38.1-1) unstable; urgency=medium
+
+  * New upstream bugfix release
+
+ -- David Bremner <bremner@debian.org>  Thu, 26 Oct 2023 19:58:42 -0300
+
+notmuch (0.38.1~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Thu, 12 Oct 2023 19:53:10 -0300
+
+notmuch (0.38.1~pre0-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sun, 01 Oct 2023 08:14:17 -0300
+
+notmuch (0.38-2) unstable; urgency=medium
+
+  * Restrict autopkgtests to amd64 and aarch64. There are failures in
+    remaining architectures, but the same tests pass at build time, so any
+    bugs are probably related to either the autopkgtest environment, or
+    the (new) upstream test runner for installed notmuch.
+
+ -- David Bremner <bremner@debian.org>  Wed, 13 Sep 2023 19:55:00 -0300
+
+notmuch (0.38-1) unstable; urgency=medium
+
+  * New upstream release
+  * Bug fix: "FTBFS: 6 tests failed.", thanks to Aurelien Jarno (Closes:
+    #1051111).
+  * Run most of upstream test suite as autopkgtests
+
+ -- David Bremner <bremner@debian.org>  Tue, 12 Sep 2023 08:33:24 -0300
+
+notmuch (0.38~rc2-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sun, 03 Sep 2023 09:10:24 -0300
+
+notmuch (0.38~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Hopefully reduce/eliminate intermittent failures of T460 by
+    controlling Emacs native compilation.
+  * Disable T810-tsan on ppc64el
+
+ -- David Bremner <bremner@debian.org>  Sat, 26 Aug 2023 08:31:21 -0300
+
+notmuch (0.38~rc0-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Thu, 24 Aug 2023 10:56:06 -0300
+
 notmuch (0.37-1) unstable; urgency=medium
 
   * New upstream release.
 notmuch (0.37-1) unstable; urgency=medium
 
   * New upstream release.
index 2dcb8cc72f7caef524b07031323b98f3fc639e55..8f59911ec1419f8195126508813cc11f325bd30d 100644 (file)
@@ -7,18 +7,18 @@ Uploaders:
  David Bremner <bremner@debian.org>,
 Build-Conflicts:
  gdb [ia64 mips mips64el hppa],
  David Bremner <bremner@debian.org>,
 Build-Conflicts:
  gdb [ia64 mips mips64el hppa],
- gdb-minimal,
  ruby1.8,
 Build-Depends:
  bash-completion (>=1.9.0~),
  debhelper-compat (= 13),
  dh-elpa (>= 1.3),
  dh-python,
  ruby1.8,
 Build-Depends:
  bash-completion (>=1.9.0~),
  debhelper-compat (= 13),
  dh-elpa (>= 1.3),
  dh-python,
+ dh-sequence-vim-addon,
  desktop-file-utils,
  doxygen,
  desktop-file-utils,
  doxygen,
- dpkg-dev (>= 1.17.14),
+ dpkg-dev (>= 1.22.5),
  dtach (>= 0.8) <!nocheck>,
  dtach (>= 0.8) <!nocheck>,
- emacs-nox | emacs-gtk | emacs-lucid | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
+ emacs-nox | emacs-gtk | emacs-lucid | emacs-pgtk | emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) | emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
  emacs-el,
  gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha !hppa] <!nocheck>,
  git <!nocheck>,
  emacs-el,
  gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha !hppa] <!nocheck>,
  git <!nocheck>,
@@ -30,7 +30,7 @@ Build-Depends:
  libtalloc-dev,
  libxapian-dev,
  libz-dev,
  libtalloc-dev,
  libxapian-dev,
  libz-dev,
- pkg-config,
+ pkgconf,
  python3,
  python3-cffi,
  python3-pytest,
  python3,
  python3-cffi,
  python3-pytest,
@@ -50,12 +50,12 @@ Rules-Requires-Root: no
 Package: notmuch
 Architecture: any
 Depends:
 Package: notmuch
 Architecture: any
 Depends:
- libnotmuch5 (= ${binary:Version}),
+ libnotmuch5t64 (= ${binary:Version}),
  ${misc:Depends},
  ${shlibs:Depends},
 Recommends:
  ${misc:Depends},
  ${shlibs:Depends},
 Recommends:
- elpa-notmuch | notmuch-vim | notmuch-mutt | alot,
- gnupg-agent,
+ elpa-notmuch | notmuch-vim | notmuch-mutt | alot | astroid,
+ gpg-agent,
  gpgsm,
 Suggests:
  mailscripts,
  gpgsm,
 Suggests:
  mailscripts,
@@ -99,7 +99,10 @@ Description: thread-based email index, search and tagging
  .
  This package contains the HTML documentation
 
  .
  This package contains the HTML documentation
 
-Package: libnotmuch5
+Package: libnotmuch5t64
+Provides: ${t64:Provides}
+Replaces: libnotmuch5
+Breaks: libnotmuch5 (<< ${source:Version})
 Section: libs
 Architecture: any
 Depends:
 Section: libs
 Architecture: any
 Depends:
@@ -120,7 +123,7 @@ Package: libnotmuch-dev
 Section: libdevel
 Architecture: any
 Depends:
 Section: libdevel
 Architecture: any
 Depends:
- libnotmuch5 (= ${binary:Version}),
+ libnotmuch5t64 (= ${binary:Version}),
  ${misc:Depends},
 Description: thread-based email index, search and tagging (development)
  Notmuch is a system for indexing, searching, reading, and tagging
  ${misc:Depends},
 Description: thread-based email index, search and tagging (development)
  Notmuch is a system for indexing, searching, reading, and tagging
@@ -131,29 +134,11 @@ Description: thread-based email index, search and tagging (development)
  This package provides the necessary development libraries and header
  files to allow you to develop new software using libnotmuch.
 
  This package provides the necessary development libraries and header
  files to allow you to develop new software using libnotmuch.
 
-Package: python3-notmuch
-Architecture: all
-Section: python
-Depends:
- libnotmuch5 (>= ${source:Version}),
- ${misc:Depends},
- ${python3:Depends},
-Description: Python 3 legacy interface to the notmuch mail search and index library
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides a legacy Python 3 interface to the notmuch
- functionality, directly interfacing with a shared notmuch library.
- .
- New projects are encouraged to use python3-notmuch2 instead.
-
 Package: python3-notmuch2
 Architecture: any
 Section: python
 Depends:
 Package: python3-notmuch2
 Architecture: any
 Section: python
 Depends:
- libnotmuch5 (>= ${source:Version}),
+ libnotmuch5t64 (>= ${source:Version}),
  ${misc:Depends},
  ${python3:Depends},
  ${shlibs:Depends},
  ${misc:Depends},
  ${python3:Depends},
  ${shlibs:Depends},
@@ -167,7 +152,7 @@ Description: Python 3 interface to the notmuch mail search and index library
  functionality using CFFI bindings, which interface with a shared
  notmuch library.
  .
  functionality using CFFI bindings, which interface with a shared
  notmuch library.
  .
- This is the preferred way to use notmuch via Python.
+ This is the current supported way to use notmuch via Python.
 
 Package: ruby-notmuch
 Architecture: any
 
 Package: ruby-notmuch
 Architecture: any
@@ -208,9 +193,9 @@ Replaces:
 Depends:
  notmuch,
  ruby-notmuch,
 Depends:
  notmuch,
  ruby-notmuch,
- vim-addon-manager,
  vim-ruby,
  ${misc:Depends},
  vim-ruby,
  ${misc:Depends},
+ ${vim-addon:Depends},
 Recommends:
  ruby-mail,
 Description: thread-based email index, search and tagging (vim interface)
 Recommends:
  ruby-mail,
 Description: thread-based email index, search and tagging (vim interface)
@@ -227,15 +212,15 @@ Architecture: all
 Depends:
  libmail-box-perl,
  libmailtools-perl,
 Depends:
  libmail-box-perl,
  libmailtools-perl,
- libstring-shellquote-perl,
  libterm-readline-gnu-perl,
  notmuch (>= 0.4),
  ${misc:Depends},
  ${perl:Depends},
 Recommends:
  libterm-readline-gnu-perl,
  notmuch (>= 0.4),
  ${misc:Depends},
  ${perl:Depends},
 Recommends:
- mutt,
+ mutt | neomutt,
 Enhances:
  mutt,
 Enhances:
  mutt,
+ neomutt,
  notmuch,
 Description: thread-based email index, search and tagging (Mutt interface)
  notmuch-mutt provides integration among the Mutt mail user agent and
  notmuch,
 Description: thread-based email index, search and tagging (Mutt interface)
  notmuch-mutt provides integration among the Mutt mail user agent and
diff --git a/debian/libnotmuch5.install b/debian/libnotmuch5.install
deleted file mode 100644 (file)
index a513b47..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/lib/*/libnotmuch.so.*
diff --git a/debian/libnotmuch5.symbols b/debian/libnotmuch5.symbols
deleted file mode 100644 (file)
index e6d2b7c..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-libnotmuch.so.5 libnotmuch5 #MINVER#
-* Build-Depends-Package: libnotmuch-dev
- notmuch_built_with@Base 0.23~rc0
- notmuch_config_get@Base 0.32~rc0
- notmuch_config_get_bool@Base 0.32~rc0
- notmuch_config_get_pairs@Base 0.32~rc0
- notmuch_config_get_values@Base 0.32~rc0
- notmuch_config_get_values_string@Base 0.32~rc0
- notmuch_config_list_destroy@Base 0.23~rc0
- notmuch_config_list_key@Base 0.23~rc0
- notmuch_config_list_move_to_next@Base 0.23~rc0
- notmuch_config_list_valid@Base 0.23~rc0
- notmuch_config_list_value@Base 0.23~rc0
- notmuch_config_pairs_destroy@Base 0.32~rc0
- notmuch_config_pairs_key@Base 0.32~rc0
- notmuch_config_pairs_move_to_next@Base 0.32~rc0
- notmuch_config_pairs_valid@Base 0.32~rc0
- notmuch_config_pairs_value@Base 0.32~rc0
- notmuch_config_path@Base 0.32~rc0
- notmuch_config_set@Base 0.32~rc0
- notmuch_config_values_destroy@Base 0.32~rc0
- notmuch_config_values_get@Base 0.32~rc0
- notmuch_config_values_move_to_next@Base 0.32~rc0
- notmuch_config_values_start@Base 0.32~rc0
- notmuch_config_values_valid@Base 0.32~rc0
- notmuch_database_add_message@Base 0.3
- notmuch_database_begin_atomic@Base 0.9~rc1
- notmuch_database_close@Base 0.13~rc1
- notmuch_database_compact@Base 0.17~rc1
- notmuch_database_compact_db@Base 0.32~rc0
- notmuch_database_create@Base 0.3
- notmuch_database_create_verbose@Base 0.20~rc1
- notmuch_database_create_with_config@Base 0.32~rc0
- notmuch_database_destroy@Base 0.13~rc1
- notmuch_database_end_atomic@Base 0.9~rc1
- notmuch_database_find_message@Base 0.9~rc2
- notmuch_database_find_message_by_filename@Base 0.9~rc2
- notmuch_database_get_all_tags@Base 0.3
- notmuch_database_get_config@Base 0.23~rc0
- notmuch_database_get_config_list@Base 0.23~rc0
- notmuch_database_get_default_indexopts@Base 0.26~rc0
- notmuch_database_get_directory@Base 0.3
- notmuch_database_get_path@Base 0.3
- notmuch_database_get_revision@Base 0.21~rc1
- notmuch_database_get_version@Base 0.3
- notmuch_database_index_file@Base 0.26~rc0
- notmuch_database_load_config@Base 0.32~rc0
- notmuch_database_needs_upgrade@Base 0.3
- notmuch_database_open@Base 0.3
- notmuch_database_open_verbose@Base 0.20~rc1
- notmuch_database_open_with_config@Base 0.32~rc0
- notmuch_database_remove_message@Base 0.3
- notmuch_database_reopen@Base 0.32~rc0
- notmuch_database_set_config@Base 0.23~rc0
- notmuch_database_status_string@Base 0.20~rc1
- notmuch_database_upgrade@Base 0.3
- notmuch_directory_delete@Base 0.21~rc1
- notmuch_directory_destroy@Base 0.3
- notmuch_directory_get_child_directories@Base 0.3
- notmuch_directory_get_child_files@Base 0.3
- notmuch_directory_get_mtime@Base 0.3
- notmuch_directory_set_mtime@Base 0.3
- notmuch_filenames_destroy@Base 0.3
- notmuch_filenames_get@Base 0.3
- notmuch_filenames_move_to_next@Base 0.3
- notmuch_filenames_valid@Base 0.3
- notmuch_indexopts_destroy@Base 0.26~rc0
- notmuch_indexopts_get_decrypt_policy@Base 0.26~rc0
- notmuch_indexopts_set_decrypt_policy@Base 0.26~rc0
- notmuch_message_add_property@Base 0.23~rc0
- notmuch_message_add_tag@Base 0.3
- notmuch_message_count_files@Base 0.26~rc0
- notmuch_message_count_properties@Base 0.27~rc0
- notmuch_message_destroy@Base 0.3
- notmuch_message_freeze@Base 0.3
- notmuch_message_get_database@Base 0.27~rc0
- notmuch_message_get_date@Base 0.3
- notmuch_message_get_filename@Base 0.3
- notmuch_message_get_filenames@Base 0.5
- notmuch_message_get_flag@Base 0.3
- notmuch_message_get_flag_st@Base 0.31~rc0
- notmuch_message_get_header@Base 0.3
- notmuch_message_get_message_id@Base 0.3
- notmuch_message_get_properties@Base 0.23~rc0
- notmuch_message_get_property@Base 0.23~rc0
- notmuch_message_get_replies@Base 0.3
- notmuch_message_get_tags@Base 0.3
- notmuch_message_get_thread_id@Base 0.3
- notmuch_message_has_maildir_flag@Base 0.26~rc0
- notmuch_message_has_maildir_flag_st@Base 0.31~rc0
- notmuch_message_maildir_flags_to_tags@Base 0.5
- notmuch_message_properties_destroy@Base 0.23~rc0
- notmuch_message_properties_key@Base 0.23~rc0
- notmuch_message_properties_move_to_next@Base 0.23~rc0
- notmuch_message_properties_valid@Base 0.23~rc0
- notmuch_message_properties_value@Base 0.23~rc0
- notmuch_message_reindex@Base 0.26~rc0
- notmuch_message_remove_all_properties@Base 0.23~rc0
- notmuch_message_remove_all_properties_with_prefix@Base 0.26~rc0
- notmuch_message_remove_all_tags@Base 0.3
- notmuch_message_remove_property@Base 0.23~rc0
- notmuch_message_remove_tag@Base 0.3
- notmuch_message_set_flag@Base 0.3
- notmuch_message_tags_to_maildir_flags@Base 0.5
- notmuch_message_thaw@Base 0.3
- notmuch_messages_collect_tags@Base 0.3
- notmuch_messages_destroy@Base 0.3
- notmuch_messages_get@Base 0.3
- notmuch_messages_move_to_next@Base 0.3
- notmuch_messages_valid@Base 0.3
- notmuch_query_add_tag_exclude@Base 0.12~rc1
- notmuch_query_count_messages@Base 0.3
- notmuch_query_count_messages_st@Base 0.21~rc1
- notmuch_query_count_threads@Base 0.10~rc1
- notmuch_query_count_threads_st@Base 0.21~rc1
- notmuch_query_create@Base 0.3
- notmuch_query_create_with_syntax@Base 0.34~rc0
- notmuch_query_destroy@Base 0.3
- notmuch_query_get_database@Base 0.21~rc1
- notmuch_query_get_query_string@Base 0.4
- notmuch_query_get_sort@Base 0.4
- notmuch_query_search_messages@Base 0.3
- notmuch_query_search_messages_st@Base 0.20~rc1
- notmuch_query_search_threads@Base 0.3
- notmuch_query_search_threads_st@Base 0.20~rc1
- notmuch_query_set_omit_excluded@Base 0.13~rc1
- notmuch_query_set_sort@Base 0.3
- notmuch_status_to_string@Base 0.3
- notmuch_tags_destroy@Base 0.3
- notmuch_tags_get@Base 0.3
- notmuch_tags_move_to_next@Base 0.3
- notmuch_tags_valid@Base 0.3
- notmuch_thread_destroy@Base 0.3
- notmuch_thread_get_authors@Base 0.3
- notmuch_thread_get_matched_messages@Base 0.3
- notmuch_thread_get_messages@Base 0.16
- notmuch_thread_get_newest_date@Base 0.3
- notmuch_thread_get_oldest_date@Base 0.3
- notmuch_thread_get_subject@Base 0.3
- notmuch_thread_get_tags@Base 0.3
- notmuch_thread_get_thread_id@Base 0.3
- notmuch_thread_get_toplevel_messages@Base 0.3
- notmuch_thread_get_total_files@Base 0.26~rc0
- notmuch_thread_get_total_messages@Base 0.3
- notmuch_threads_destroy@Base 0.3
- notmuch_threads_get@Base 0.3
- notmuch_threads_move_to_next@Base 0.3
- notmuch_threads_valid@Base 0.3
- (c++)"typeinfo for Xapian::LogicError@Base" 0.6.1
- (c++)"typeinfo for Xapian::RuntimeError@Base" 0.6.1
- (c++)"typeinfo for Xapian::DocNotFoundError@Base" 0.6.1
- (c++)"typeinfo for Xapian::InvalidArgumentError@Base" 0.6.1
- (c++)"typeinfo for Xapian::Error@Base" 0.6.1
- (c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0
- (c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0
- (c++)"typeinfo for Xapian::DatabaseOpeningError@Base" 0.32~rc0
- (c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0
- (c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::DocNotFoundError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::InvalidArgumentError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::Error@Base" 0.6.1
- (c++)"typeinfo name for Xapian::DatabaseError@Base" 0.24~rc0
- (c++)"typeinfo name for Xapian::DatabaseModifiedError@Base" 0.24~rc0
- (c++)"typeinfo name for Xapian::DatabaseOpeningError@Base" 0.32~rc0
- (c++|optional=present with Xapian 1.4)"typeinfo name for Xapian::QueryParserError@Base" 0.23~rc0
diff --git a/debian/libnotmuch5t64.install b/debian/libnotmuch5t64.install
new file mode 100644 (file)
index 0000000..a513b47
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libnotmuch.so.*
diff --git a/debian/libnotmuch5t64.lintian-overrides b/debian/libnotmuch5t64.lintian-overrides
new file mode 100644 (file)
index 0000000..affb63b
--- /dev/null
@@ -0,0 +1 @@
+libnotmuch5t64: package-name-doesnt-match-sonames libnotmuch5
diff --git a/debian/libnotmuch5t64.symbols b/debian/libnotmuch5t64.symbols
new file mode 100644 (file)
index 0000000..5715dec
--- /dev/null
@@ -0,0 +1,166 @@
+libnotmuch.so.5 libnotmuch5t64 #MINVER#
+* Build-Depends-Package: libnotmuch-dev
+ notmuch_built_with@Base 0.23~rc0
+ notmuch_config_get@Base 0.32~rc0
+ notmuch_config_get_bool@Base 0.32~rc0
+ notmuch_config_get_pairs@Base 0.32~rc0
+ notmuch_config_get_values@Base 0.32~rc0
+ notmuch_config_get_values_string@Base 0.32~rc0
+ notmuch_config_list_destroy@Base 0.23~rc0
+ notmuch_config_list_key@Base 0.23~rc0
+ notmuch_config_list_move_to_next@Base 0.23~rc0
+ notmuch_config_list_valid@Base 0.23~rc0
+ notmuch_config_list_value@Base 0.23~rc0
+ notmuch_config_pairs_destroy@Base 0.32~rc0
+ notmuch_config_pairs_key@Base 0.32~rc0
+ notmuch_config_pairs_move_to_next@Base 0.32~rc0
+ notmuch_config_pairs_valid@Base 0.32~rc0
+ notmuch_config_pairs_value@Base 0.32~rc0
+ notmuch_config_path@Base 0.32~rc0
+ notmuch_config_set@Base 0.32~rc0
+ notmuch_config_values_destroy@Base 0.32~rc0
+ notmuch_config_values_get@Base 0.32~rc0
+ notmuch_config_values_move_to_next@Base 0.32~rc0
+ notmuch_config_values_start@Base 0.32~rc0
+ notmuch_config_values_valid@Base 0.32~rc0
+ notmuch_database_add_message@Base 0.3
+ notmuch_database_begin_atomic@Base 0.9~rc1
+ notmuch_database_close@Base 0.13~rc1
+ notmuch_database_compact@Base 0.17~rc1
+ notmuch_database_compact_db@Base 0.32~rc0
+ notmuch_database_create@Base 0.3
+ notmuch_database_create_verbose@Base 0.20~rc1
+ notmuch_database_create_with_config@Base 0.32~rc0
+ notmuch_database_destroy@Base 0.13~rc1
+ notmuch_database_end_atomic@Base 0.9~rc1
+ notmuch_database_find_message@Base 0.9~rc2
+ notmuch_database_find_message_by_filename@Base 0.9~rc2
+ notmuch_database_get_all_tags@Base 0.3
+ notmuch_database_get_config@Base 0.23~rc0
+ notmuch_database_get_config_list@Base 0.23~rc0
+ notmuch_database_get_default_indexopts@Base 0.26~rc0
+ notmuch_database_get_directory@Base 0.3
+ notmuch_database_get_path@Base 0.3
+ notmuch_database_get_revision@Base 0.21~rc1
+ notmuch_database_get_version@Base 0.3
+ notmuch_database_index_file@Base 0.26~rc0
+ notmuch_database_load_config@Base 0.32~rc0
+ notmuch_database_needs_upgrade@Base 0.3
+ notmuch_database_open@Base 0.3
+ notmuch_database_open_verbose@Base 0.20~rc1
+ notmuch_database_open_with_config@Base 0.32~rc0
+ notmuch_database_remove_message@Base 0.3
+ notmuch_database_reopen@Base 0.32~rc0
+ notmuch_database_set_config@Base 0.23~rc0
+ notmuch_database_status_string@Base 0.20~rc1
+ notmuch_database_upgrade@Base 0.3
+ notmuch_directory_delete@Base 0.21~rc1
+ notmuch_directory_destroy@Base 0.3
+ notmuch_directory_get_child_directories@Base 0.3
+ notmuch_directory_get_child_files@Base 0.3
+ notmuch_directory_get_mtime@Base 0.3
+ notmuch_directory_set_mtime@Base 0.3
+ notmuch_filenames_destroy@Base 0.3
+ notmuch_filenames_get@Base 0.3
+ notmuch_filenames_move_to_next@Base 0.3
+ notmuch_filenames_valid@Base 0.3
+ notmuch_indexopts_destroy@Base 0.26~rc0
+ notmuch_indexopts_get_decrypt_policy@Base 0.26~rc0
+ notmuch_indexopts_set_decrypt_policy@Base 0.26~rc0
+ notmuch_message_add_property@Base 0.23~rc0
+ notmuch_message_add_tag@Base 0.3
+ notmuch_message_count_files@Base 0.26~rc0
+ notmuch_message_count_properties@Base 0.27~rc0
+ notmuch_message_destroy@Base 0.3
+ notmuch_message_freeze@Base 0.3
+ notmuch_message_get_database@Base 0.27~rc0
+ notmuch_message_get_date@Base 0.3
+ notmuch_message_get_filename@Base 0.3
+ notmuch_message_get_filenames@Base 0.5
+ notmuch_message_get_flag@Base 0.3
+ notmuch_message_get_flag_st@Base 0.31~rc0
+ notmuch_message_get_header@Base 0.3
+ notmuch_message_get_message_id@Base 0.3
+ notmuch_message_get_properties@Base 0.23~rc0
+ notmuch_message_get_property@Base 0.23~rc0
+ notmuch_message_get_replies@Base 0.3
+ notmuch_message_get_tags@Base 0.3
+ notmuch_message_get_thread_id@Base 0.3
+ notmuch_message_has_maildir_flag@Base 0.26~rc0
+ notmuch_message_has_maildir_flag_st@Base 0.31~rc0
+ notmuch_message_maildir_flags_to_tags@Base 0.5
+ notmuch_message_properties_destroy@Base 0.23~rc0
+ notmuch_message_properties_key@Base 0.23~rc0
+ notmuch_message_properties_move_to_next@Base 0.23~rc0
+ notmuch_message_properties_valid@Base 0.23~rc0
+ notmuch_message_properties_value@Base 0.23~rc0
+ notmuch_message_reindex@Base 0.26~rc0
+ notmuch_message_remove_all_properties@Base 0.23~rc0
+ notmuch_message_remove_all_properties_with_prefix@Base 0.26~rc0
+ notmuch_message_remove_all_tags@Base 0.3
+ notmuch_message_remove_property@Base 0.23~rc0
+ notmuch_message_remove_tag@Base 0.3
+ notmuch_message_set_flag@Base 0.3
+ notmuch_message_tags_to_maildir_flags@Base 0.5
+ notmuch_message_thaw@Base 0.3
+ notmuch_messages_collect_tags@Base 0.3
+ notmuch_messages_destroy@Base 0.3
+ notmuch_messages_get@Base 0.3
+ notmuch_messages_move_to_next@Base 0.3
+ notmuch_messages_valid@Base 0.3
+ notmuch_query_add_tag_exclude@Base 0.12~rc1
+ notmuch_query_count_messages@Base 0.3
+ notmuch_query_count_messages_st@Base 0.21~rc1
+ notmuch_query_count_threads@Base 0.10~rc1
+ notmuch_query_count_threads_st@Base 0.21~rc1
+ notmuch_query_create@Base 0.3
+ notmuch_query_create_with_syntax@Base 0.34~rc0
+ notmuch_query_destroy@Base 0.3
+ notmuch_query_get_database@Base 0.21~rc1
+ notmuch_query_get_query_string@Base 0.4
+ notmuch_query_get_sort@Base 0.4
+ notmuch_query_search_messages@Base 0.3
+ notmuch_query_search_messages_st@Base 0.20~rc1
+ notmuch_query_search_threads@Base 0.3
+ notmuch_query_search_threads_st@Base 0.20~rc1
+ notmuch_query_set_omit_excluded@Base 0.13~rc1
+ notmuch_query_set_sort@Base 0.3
+ notmuch_status_to_string@Base 0.3
+ notmuch_tags_destroy@Base 0.3
+ notmuch_tags_get@Base 0.3
+ notmuch_tags_move_to_next@Base 0.3
+ notmuch_tags_valid@Base 0.3
+ notmuch_thread_destroy@Base 0.3
+ notmuch_thread_get_authors@Base 0.3
+ notmuch_thread_get_matched_messages@Base 0.3
+ notmuch_thread_get_messages@Base 0.16
+ notmuch_thread_get_newest_date@Base 0.3
+ notmuch_thread_get_oldest_date@Base 0.3
+ notmuch_thread_get_subject@Base 0.3
+ notmuch_thread_get_tags@Base 0.3
+ notmuch_thread_get_thread_id@Base 0.3
+ notmuch_thread_get_toplevel_messages@Base 0.3
+ notmuch_thread_get_total_files@Base 0.26~rc0
+ notmuch_thread_get_total_messages@Base 0.3
+ notmuch_threads_destroy@Base 0.3
+ notmuch_threads_get@Base 0.3
+ notmuch_threads_move_to_next@Base 0.3
+ notmuch_threads_valid@Base 0.3
+ (c++)"typeinfo for Xapian::LogicError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::RuntimeError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::DocNotFoundError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::InvalidArgumentError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::Error@Base" 0.6.1
+ (c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0
+ (c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0
+ (c++)"typeinfo for Xapian::DatabaseOpeningError@Base" 0.32~rc0
+ (c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0
+ (c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::DocNotFoundError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::InvalidArgumentError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::Error@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::DatabaseError@Base" 0.24~rc0
+ (c++)"typeinfo name for Xapian::DatabaseModifiedError@Base" 0.24~rc0
+ (c++)"typeinfo name for Xapian::DatabaseOpeningError@Base" 0.32~rc0
+ (c++|optional=present with Xapian 1.4)"typeinfo name for Xapian::QueryParserError@Base" 0.23~rc0
diff --git a/debian/notmuch-vim.dirs b/debian/notmuch-vim.dirs
deleted file mode 100644 (file)
index 2b53131..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-usr/share/vim/addons/doc
-usr/share/vim/addons/plugin
-usr/share/vim/addons/syntax
-usr/share/vim/registry
index cf8987385a097665fdb84df8aa2f1eb8e2d724f4..08096d37ef958c0cae4acb5f2de8a8742454bc0d 100644 (file)
@@ -1,4 +1 @@
-vim/notmuch.txt usr/share/vim/addons/doc
-vim/notmuch.vim usr/share/vim/addons/plugin
-vim/notmuch.yaml usr/share/vim/registry
-vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax
+/usr/share/notmuch-vim
diff --git a/debian/notmuch-vim.neovim-addon b/debian/notmuch-vim.neovim-addon
new file mode 120000 (symlink)
index 0000000..a576db7
--- /dev/null
@@ -0,0 +1 @@
+notmuch-vim.vim-addon
\ No newline at end of file
diff --git a/debian/notmuch-vim.vim-addon b/debian/notmuch-vim.vim-addon
new file mode 100644 (file)
index 0000000..9c23ba1
--- /dev/null
@@ -0,0 +1 @@
+/usr/share/notmuch-vim notmuch
index f9196b82612001ee89a4a1d5753c29901c9ba9d2..4e0836e759c3ac56ee65365a6a28cf6096421a81 100755 (executable)
@@ -1,5 +1,8 @@
 #!/usr/bin/make -f
 #!/usr/bin/make -f
+include /usr/share/dpkg/architecture.mk
 
 
+export NOTMUCH_SKIP_TESTS = T810-tsan
+export NOTMUCH_TEST_TIMEOUT = 10m
 export DEB_BUILD_MAINT_OPTIONS = hardening=+all
 
 %:
 export DEB_BUILD_MAINT_OPTIONS = hardening=+all
 
 %:
@@ -7,7 +10,7 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
 
 override_dh_auto_configure:
        BASHCMD=/bin/bash ./configure --prefix=/usr \
 
 override_dh_auto_configure:
        BASHCMD=/bin/bash ./configure --prefix=/usr \
-               --libdir=/usr/lib/$$(dpkg-architecture -q DEB_TARGET_MULTIARCH) \
+               --libdir=/usr/lib/${DEB_TARGET_MULTIARCH} \
                --includedir=/usr/include \
                --mandir=/usr/share/man \
                --infodir=/usr/share/info \
                --includedir=/usr/include \
                --mandir=/usr/share/man \
                --infodir=/usr/share/info \
@@ -17,20 +20,17 @@ override_dh_auto_configure:
 
 override_dh_auto_build:
        dh_auto_build -- V=1 all sphinx-html
 
 override_dh_auto_build:
        dh_auto_build -- V=1 all sphinx-html
-       PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python
        PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        $(MAKE) -C contrib/notmuch-mutt
 
 override_dh_auto_clean:
        dh_auto_clean
        PYBUILD_NAME=notmuch2 dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        $(MAKE) -C contrib/notmuch-mutt
 
 override_dh_auto_clean:
        dh_auto_clean
-       PYBUILD_NAME=notmuch dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python
-       PYBUILD_NAME=notmuch2 dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        dh_auto_clean --sourcedirectory bindings/ruby
        $(MAKE) -C contrib/notmuch-mutt clean
 
 override_dh_auto_install:
        dh_auto_install
        dh_auto_clean --sourcedirectory bindings/ruby
        $(MAKE) -C contrib/notmuch-mutt clean
 
 override_dh_auto_install:
        dh_auto_install
-       PYBUILD_NAME=notmuch dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python
        PYBUILD_NAME=notmuch2 dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        $(MAKE) -C contrib/notmuch-mutt DESTDIR=$(CURDIR)/debian/tmp install
        PYBUILD_NAME=notmuch2 dh_auto_install --buildsystem=pybuild --sourcedirectory bindings/python-cffi
        $(MAKE) -C contrib/notmuch-mutt DESTDIR=$(CURDIR)/debian/tmp install
+       $(MAKE) -C vim prefix=/usr/share/notmuch-vim DESTDIR=$(CURDIR)/debian/tmp install
        dh_auto_install --sourcedirectory bindings/ruby
        dh_auto_install --sourcedirectory bindings/ruby
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644 (file)
index 0000000..11aebad
--- /dev/null
@@ -0,0 +1,18 @@
+Test-command: env NOTMUCH_TEST_INSTALLED=1 TERM=dumb
+    NOTMUCH_HAVE_MAN=1 NOTMUCH_HAVE_SFSEXP=1 NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=1
+    NOTMUCH_HAVE_PYTHON3_CFFI=1 NOTMUCH_HAVE_PYTHON3_PYTEST=1
+    NOTMUCH_HAVE_ASAN=1 NOTMUCH_HAVE_TSAN=1 NOTMUCH_SKIP_TESTS=T810-tsan
+    ./test/notmuch-test
+Restrictions: allow-stderr
+Architecture: amd64, arm64
+Depends: @,
+ build-essential,
+ dtach,
+ emacs-nox,
+ gdb,
+ git,
+ gnupg,
+ gpgsm,
+ libtalloc-dev,
+ man,
+ xapian-tools
index ad7f72ef0bb3ce039a9bd3fd22988c93b80565c6..ac98cb663855bee6b6e2f67d97ab8b6d998962a5 100644 (file)
@@ -1,60 +1,61 @@
-|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
-| Key          | Search Mode                            | Show Mode                                             | Tree Mode                               |
-|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
-| a            | notmuch-search-archive-thread          | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next  |
-| b            | notmuch-search-scroll-down             | notmuch-show-resend-message                           | notmuch-show-resend-message             |
-| c            | notmuch-search-stash-map               | notmuch-show-stash-map                                | notmuch-show-stash-map                  |
-| d            |                                        |                                                       |                                         |
-| e            |                                        |                                                       | (notmuch-tree-button-activate)          |
-| f            |                                        | notmuch-show-forward-message                          | notmuch-show-forward-message            |
-| g            |                                        |                                                       |                                         |
-| h            |                                        | notmuch-show-toggle-visibility-headers                |                                         |
-| i            |                                        |                                                       |                                         |
-| j            | notmuch-jump-search                    | notmuch-jump-search                                   | notmuch-jump-search                     |
-| k            | notmuch-tag-jump                       | notmuch-tag-jump                                      | notmuch-tag-jump                        |
-| l            | notmuch-search-filter                  | notmuch-show-filter-thread                            | notmuch-tree-filter                     |
-| m            | notmuch-mua-new-mail                   | notmuch-mua-new-mail                                  | notmuch-mua-new-mail                    |
-| n            | notmuch-search-next-thread             | notmuch-show-next-open-message                        | notmuch-tree-next-matching-message      |
-| o            | notmuch-search-toggle-order            |                                                       | notmuch-tree-toggle-order               |
-| p            | notmuch-search-previous-thread         | notmuch-show-previous-open-message                    | notmuch-tree-prev-matching-message      |
-| q            | notmuch-bury-or-kill-this-buffer       | notmuch-bury-or-kill-this-buffer                      | notmuch-bury-or-kill-this-buffer        |
-| r            | notmuch-search-reply-to-thread-sender  | notmuch-show-reply-sender                             | notmuch-show-reply-sender               |
-| s            | notmuch-search                         | notmuch-search                                        | notmuch-search                          |
-| t            | notmuch-search-filter-by-tag           | toggle-truncate-lines                                 | notmuch-tree-filter-by-tag              |
-| u            |                                        |                                                       |                                         |
-| v            |                                        |                                                       | notmuch-show-view-all-mime-parts        |
-| w            |                                        | notmuch-show-save-attachments                         | notmuch-show-save-attachments           |
-| x            | notmuch-bury-or-kill-this-buffer       | notmuch-show-archive-message-then-next-or-exit        | notmuch-tree-quit                       |
-| y            |                                        |                                                       |                                         |
-| z            | notmuch-tree                           | notmuch-tree                                          | notmuch-tree-to-tree                    |
-| A            |                                        | notmuch-show-archive-thread-then-next                 | notmuch-tree-archive-thread             |
-| F            |                                        | notmuch-show-forward-open-messages                    |                                         |
-| G            | notmuch-poll-and-refresh-this-buffer   | notmuch-poll-and-refresh-this-buffer                  | notmuch-poll-and-refresh-this-buffer    |
-| N            |                                        | notmuch-show-next-message                             | notmuch-tree-next-message               |
-| O            |                                        |                                                       |                                         |
-| P            |                                        | notmuch-show-previous-message                         | notmuch-tree-prev-message               |
-| R            | notmuch-search-reply-to-thread         | notmuch-show-reply                                    | notmuch-show-reply                      |
-| S            |                                        |                                                       | notmuch-search-from-tree-current-query  |
-| V            |                                        | notmuch-show-view-raw-message                         | notmuch-show-view-raw-message           |
-| X            |                                        | notmuch-show-archive-thread-then-exit                 |                                         |
-| Z            | notmuch-tree-from-search-current-query | notmuch-tree-from-show-current-query                  |                                         |
-| =!=          |                                        | notmuch-show-toggle-elide-non-matching                |                                         |
-| =#=          |                                        | notmuch-show-print-message                            |                                         |
-| =%=          |                                        | notmuch-show-replace-msg                              |                                         |
-| =$=          |                                        | notmuch-show-toggle-process-crypto                    |                                         |
-| =*=          | notmuch-search-tag-all                 | notmuch-show-tag-all                                  | notmuch-tree-tag-thread                 |
-| +            | notmuch-search-add-tag                 | notmuch-show-add-tag                                  | notmuch-tree-add-tag                    |
-| -            | notmuch-search-remove-tag              | notmuch-show-remove-tag                               | notmuch-tree-remove-tag                 |
-| .            |                                        | notmuch-show-part-map                                 |                                         |
-| <            | notmuch-search-first-thread            | notmuch-show-toggle-thread-indentation                |                                         |
-| <DEL>        | notmuch-search-scroll-down             | notmuch-show-rewind                                   | notmuch-tree-scroll-message-window-back |
-| <RET>        | notmuch-search-show-thread             | notmuch-show-toggle-message                           | notmuch-tree-show-message               |
-| <SPC>        | notmuch-search-scroll-up               | notmuch-show-advance                                  | notmuch-tree-scroll-or-next             |
-| <TAB>        |                                        | notmuch-show-next-button                              | notmuch-show-next-button                |
-| <backtab>    |                                        | notmuch-show-previous-button                          | notmuch-show-previous-button            |
-| =            | notmuch-refresh-this-buffer            | notmuch-refresh-this-buffer                           | notmuch-tree-refresh-view               |
-| >            | notmuch-search-last-thread             |                                                       |                                         |
-| ?            | notmuch-help                           | notmuch-help                                          | notmuch-help                            |
-| \vert        |                                        | notmuch-show-pipe-message                             | notmuch-show-pipe-message               |
-| [remap undo] | notmuch-tag-undo                       | notmuch-tag-undo                                      | notmuch-tag-undo                        |
-|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
+|--------------+----------------------------------------------+-------------------------------------------------------+--------------------------------------------|
+| Key          | Search Mode                                  | Show Mode                                             | Tree Mode                                  |
+|--------------+----------------------------------------------+-------------------------------------------------------+--------------------------------------------|
+| a            | notmuch-search-archive-thread                | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next     |
+| b            | notmuch-search-scroll-down                   | notmuch-show-resend-message                           | notmuch-show-resend-message                |
+| c            | notmuch-search-stash-map                     | notmuch-show-stash-map                                | notmuch-show-stash-map                     |
+| d            |                                              |                                                       |                                            |
+| e            |                                              | notmuch-show-resume-message                           | notmuch-tree-resume-message                |
+| f            |                                              | notmuch-show-forward-message                          | notmuch-show-forward-message               |
+| g            |                                              |                                                       |                                            |
+| h            |                                              | notmuch-show-toggle-visibility-headers                |                                            |
+| i            | notmuch-search-toggle-hide-excluded          |                                                       | notmuch-tree-toggle-hide-excluded          |
+| j            | notmuch-jump-search                          | notmuch-jump-search                                   | notmuch-jump-search                        |
+| k            | notmuch-tag-jump                             | notmuch-tag-jump                                      | notmuch-tag-jump                           |
+| l            | notmuch-search-filter                        | notmuch-show-filter-thread                            | notmuch-tree-filter                        |
+| m            | notmuch-mua-new-mail                         | notmuch-mua-new-mail                                  | notmuch-mua-new-mail                       |
+| n            | notmuch-search-next-thread                   | notmuch-show-next-open-message                        | notmuch-tree-next-matching-message         |
+| o            | notmuch-search-toggle-order                  |                                                       | notmuch-tree-toggle-order                  |
+| p            | notmuch-search-previous-thread               | notmuch-show-previous-open-message                    | notmuch-tree-prev-matching-message         |
+| q            | notmuch-bury-or-kill-this-buffer             | notmuch-bury-or-kill-this-buffer                      | notmuch-bury-or-kill-this-buffer           |
+| r            | notmuch-search-reply-to-thread-sender        | notmuch-show-reply-sender                             | notmuch-show-reply-sender                  |
+| s            | notmuch-search                               | notmuch-search                                        | notmuch-search                             |
+| t            | notmuch-search-filter-by-tag                 | toggle-truncate-lines                                 | notmuch-tree-filter-by-tag                 |
+| u            | notmuch-unthreaded                           | notmuch-unthreaded                                    | notmuch-unthreaded                         |
+| v            |                                              |                                                       | notmuch-show-view-all-mime-parts           |
+| w            |                                              | notmuch-show-save-attachments                         | notmuch-show-save-attachments              |
+| x            | notmuch-bury-or-kill-this-buffer             | notmuch-show-archive-message-then-next-or-exit        | notmuch-tree-quit                          |
+| y            |                                              |                                                       |                                            |
+| z            | notmuch-tree                                 | notmuch-tree                                          | notmuch-tree-to-tree                       |
+| A            |                                              | notmuch-show-archive-thread-then-next                 | notmuch-tree-archive-thread                |
+| F            |                                              | notmuch-show-forward-open-messages                    |                                            |
+| G            | notmuch-poll-and-refresh-this-buffer         | notmuch-poll-and-refresh-this-buffer                  | notmuch-poll-and-refresh-this-buffer       |
+| N            |                                              | notmuch-show-next-message                             | notmuch-tree-next-message                  |
+| O            |                                              |                                                       |                                            |
+| P            |                                              | notmuch-show-previous-message                         | notmuch-tree-prev-message                  |
+| R            | notmuch-search-reply-to-thread               | notmuch-show-reply                                    | notmuch-show-reply                         |
+| S            |                                              |                                                       | notmuch-search-from-tree-current-query     |
+| U            | notmuch-unthreaded-from-search-current-query | notmuch-unthreaded-from-show-current-query            | notmuch-unthreaded-from-tree-current-query |
+| V            |                                              | notmuch-show-view-raw-message                         | notmuch-show-view-raw-message              |
+| X            |                                              | notmuch-show-archive-thread-then-exit                 |                                            |
+| Z            | notmuch-tree-from-search-current-query       | notmuch-tree-from-show-current-query                  |                                            |
+| =!=          |                                              | notmuch-show-toggle-elide-non-matching                |                                            |
+| =#=          |                                              | notmuch-show-print-message                            |                                            |
+| =%=          |                                              | notmuch-show-replace-msg                              |                                            |
+| =$=          |                                              | notmuch-show-toggle-process-crypto                    |                                            |
+| =*=          | notmuch-search-tag-all                       | notmuch-show-tag-all                                  | notmuch-tree-tag-thread                    |
+| +            | notmuch-search-add-tag                       | notmuch-show-add-tag                                  | notmuch-tree-add-tag                       |
+| -            | notmuch-search-remove-tag                    | notmuch-show-remove-tag                               | notmuch-tree-remove-tag                    |
+| .            |                                              | notmuch-show-part-map                                 |                                            |
+| <            | notmuch-search-first-thread                  | notmuch-show-toggle-thread-indentation                |                                            |
+| <DEL>        | notmuch-search-scroll-down                   | notmuch-show-rewind                                   | notmuch-tree-scroll-message-window-back    |
+| <RET>        | notmuch-search-show-thread                   | notmuch-show-toggle-message                           | notmuch-tree-show-message                  |
+| <SPC>        | notmuch-search-scroll-up                     | notmuch-show-advance                                  | notmuch-tree-scroll-or-next                |
+| <TAB>        |                                              | notmuch-show-next-button                              | notmuch-show-next-button                   |
+| <backtab>    |                                              | notmuch-show-previous-button                          | notmuch-show-previous-button               |
+| =            | notmuch-refresh-this-buffer                  | notmuch-refresh-this-buffer                           | notmuch-tree-refresh-view                  |
+| >            | notmuch-search-last-thread                   |                                                       |                                            |
+| ?            | notmuch-help                                 | notmuch-help                                          | notmuch-help                               |
+| \vert        |                                              | notmuch-show-pipe-message                             | notmuch-show-pipe-message                  |
+| [remap undo] | notmuch-tag-undo                             | notmuch-tag-undo                                      | notmuch-tag-undo                           |
+|--------------+----------------------------------------------+-------------------------------------------------------+--------------------------------------------|
index 7b555c62b6eeadd3deadaa2cec5584fa4edf535f..e0e87b49b2ca2c0de3fc29a36e58fd498841c960 100755 (executable)
@@ -125,7 +125,7 @@ def mailto_addrs(msg,header_name):
     return ''
 
   frm = email.utils.getaddresses([hdr])
     return ''
 
   frm = email.utils.getaddresses([hdr])
-  return ','.join(['<a href="mailto:%s">%s</a> ' % ((l, p) if p else (l, l)) for (p, l) in frm])
+  return ', '.join(['<a href="mailto:%s">%s</a>' % ((l, p) if p else (l, l)) for (p, l) in frm])
 env.globals['mailto_addrs'] = mailto_addrs
 
 def link_msg(msg):
 env.globals['mailto_addrs'] = mailto_addrs
 
 def link_msg(msg):
@@ -207,7 +207,7 @@ env.globals['thread_nav'] = thread_nav
 
 def format_message(nm_msg, mid):
   fn = list(nm_msg.filenames())[0]
 
 def format_message(nm_msg, mid):
   fn = list(nm_msg.filenames())[0]
-  msg = MaildirMessage(open(fn))
+  msg = MaildirMessage(open(fn, 'rb'))
   return format_message_walk(msg, mid)
 
 def decodeAnyway(txt, charset='ascii'):
   return format_message_walk(msg, mid)
 
 def decodeAnyway(txt, charset='ascii'):
index 98d36accb95b6c6325e39e8eb8ef7fd6e8513ec4..690f5464ef053a1baf24edec1235b56e0bbe344c 100644 (file)
@@ -3,10 +3,10 @@
 {% set headers = ['Subject', 'Date'] %}
 {% set addr_headers = ['To', 'Cc', 'From'] %}
 {% for header in headers: %}
 {% set headers = ['Subject', 'Date'] %}
 {% set addr_headers = ['To', 'Cc', 'From'] %}
 {% for header in headers: %}
-<p><b>{{header}}:</b>{{m.header(header)|e}}</p>
+<p><b>{{header}}:</b> {{m.header(header)|e}}</p>
 {% endfor %}
 {% for header in addr_headers: %}
 {% endfor %}
 {% for header in addr_headers: %}
-<p><b>{{header}}:</b>{{mailto_addrs(m,header)|safe}}</p>
+<p><b>{{header}}:</b> {{mailto_addrs(m,header)|safe}}</p>
 {% endfor %}
 <hr>
 {% for part in format_message(m,mid): %}{{ part|safe }}{% endfor %}
 {% endfor %}
 <hr>
 {% for part in format_message(m,mid): %}{{ part|safe }}{% endfor %}
index c0accf78bf1fa3ae86ac2586c122dab9c1663499..4f81722bb85a2f4cc869ec2256d616423d877521 100755 (executable)
@@ -18,8 +18,6 @@ readonly DEFAULT_IFS="$IFS" # Note: In this particular case quotes are needed.
 # Avoid locale-specific differences in output of executed commands
 LANG=C LC_ALL=C; export LANG LC_ALL
 
 # Avoid locale-specific differences in output of executed commands
 LANG=C LC_ALL=C; export LANG LC_ALL
 
-readonly PV_FILE='bindings/python/notmuch/version.py'
-
 # Using array here turned out to be unnecessarily complicated
 emsgs=''
 emsg_count=0
 # Using array here turned out to be unnecessarily complicated
 emsgs=''
 emsg_count=0
@@ -29,7 +27,7 @@ append_emsg ()
        emsgs="${emsgs:+$emsgs\n}  $1"
 }
 
        emsgs="${emsgs:+$emsgs\n}  $1"
 }
 
-for f in ./version.txt debian/changelog NEWS "$PV_FILE"
+for f in ./version.txt debian/changelog NEWS
 do
        if   [ ! -f "$f" ]; then append_emsg "File '$f' is missing"
        elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable"
 do
        if   [ ! -f "$f" ]; then append_emsg "File '$f' is missing"
        elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable"
@@ -108,16 +106,6 @@ else
        append_emsg "Version '$deb_version' is not '($VERSION-1)' in debian/changelog"
 fi
 
        append_emsg "Version '$deb_version' is not '($VERSION-1)' in debian/changelog"
 fi
 
-printf %s "Checking that python bindings version is $VERSION... "
-py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
-if [ "$py_version" = "$VERSION" ]
-then
-       echo Yes.
-else
-       echo No.
-       append_emsg "Version '$py_version' is not '$VERSION' in $PV_FILE"
-fi
-
 printf %s "Checking that NEWS header is tidy... "
 if [ "`exec sed 's/./=/g; 1q' NEWS`" = "`exec sed '1d; 2q' NEWS`" ]
 then
 printf %s "Checking that NEWS header is tidy... "
 if [ "`exec sed 's/./=/g; 1q' NEWS`" = "`exec sed '1d; 2q' NEWS`" ]
 then
index 66bcdbed8d0146967b10260bfb6a599cc3bb63b0..4e05cdacdf0419718f94065a7c58169ca9138afc 100644 (file)
@@ -76,6 +76,7 @@ message = {
     # (format_message_sprinter)
     id:             messageid,
     match:          bool,
     # (format_message_sprinter)
     id:             messageid,
     match:          bool,
+    excluded:       bool,
     filename:      [string*],
     timestamp:      unix_time, # date header as unix time
     date_relative:  string,   # user-friendly timestamp
     filename:      [string*],
     timestamp:      unix_time, # date header as unix time
     date_relative:  string,   # user-friendly timestamp
index 51c729cf7fe5d61ef5719e25c797ecff62d40af5..aafa77a0fe229e7dc2f8435e6e7791c5dc8619ae 100644 (file)
@@ -159,3 +159,5 @@ $(dir)/config.dox: version.stamp
 CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp
 CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.info.stamp
 CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
 CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp
 CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.info.stamp
 CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
+
+CLEAN := $(CLEAN) $(dir)/__pycache__
index e23cb7d7fb444964cb341269771629713f8c2d2e..1cc9961e1ef7e26b12a945754472a4b088f30a73 100644 (file)
@@ -16,7 +16,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'notmuch'
 
 # General information about the project.
 project = u'notmuch'
-copyright = u'2009-2022, Carl Worth and many others'
+copyright = u'2009-2025, Carl Worth and many others'
 
 location = os.path.dirname(__file__)
 
 
 location = os.path.dirname(__file__)
 
@@ -47,7 +47,7 @@ if tags.has('WITH_EMACS'):
     # Hacky reimplementation of include to workaround limitations of
     # sphinx-doc
     lines = ['.. include:: /../emacs/rstdoc.rsti\n\n'] # in the source tree
     # Hacky reimplementation of include to workaround limitations of
     # sphinx-doc
     lines = ['.. include:: /../emacs/rstdoc.rsti\n\n'] # in the source tree
-    for file in ('notmuch.rsti', 'notmuch-lib.rsti', 'notmuch-hello.rsti', 'notmuch-show.rsti', 'notmuch-tag.rsti', 'notmuch-tree.rsti'):
+    for file in ('notmuch.rsti', 'notmuch-lib.rsti', 'notmuch-hello.rsti', 'notmuch-show.rsti', 'notmuch-tag.rsti', 'notmuch-tree.rsti', 'notmuch-maildir-fcc.rsti'):
         lines.extend(open(rsti_dir+'/'+file))
     rst_epilog = ''.join(lines)
     del lines
         lines.extend(open(rsti_dir+'/'+file))
     rst_epilog = ''.join(lines)
     del lines
index 388315f67ec609c396ee0168c7e9741b9990bbec..5106655f40d283fbccc3554ea272d6f55ce217a0 100644 (file)
@@ -122,6 +122,16 @@ paths are presumed relative to `$HOME` for items in section
 
     Default tag prefix (filter) for :any:`notmuch-git`.
 
 
     Default tag prefix (filter) for :any:`notmuch-git`.
 
+.. nmconfig:: index.as_text
+
+   List of regular expressions (without delimiters) for MIME types to
+   be indexed as text. Currently this applies only to attachments.  By
+   default the regex matches anywhere in the content type; if the
+   user wants an anchored match, they should include anchors in their
+   regexes.
+
+   History: This configuration value was introduced in notmuch 0.38.
+
 .. nmconfig:: index.decrypt
 
     Policy for decrypting encrypted messages during indexing.  Must be
 .. nmconfig:: index.decrypt
 
     Policy for decrypting encrypted messages during indexing.  Must be
@@ -263,6 +273,19 @@ paths are presumed relative to `$HOME` for items in section
     Default: empty list. Note that :any:`notmuch-setup(1)` puts
     ``deleted;spam`` here when creating new configuration file.
 
     Default: empty list. Note that :any:`notmuch-setup(1)` puts
     ``deleted;spam`` here when creating new configuration file.
 
+.. nmconfig:: search.authors_separator
+
+    The string to separate authors when showing a thread.
+
+    Default: ,
+
+.. nmconfig:: search.authors_matched_separator
+
+    The string to separate matched from non-matched authors when showing a
+    thread.
+
+    Default: |
+
 .. nmconfig:: show.extra_headers
 
     By default :any:`notmuch-show(1)` includes the following headers
 .. nmconfig:: show.extra_headers
 
     By default :any:`notmuch-show(1)` includes the following headers
index ac1908b6103f17be1aa2e995f4dffbf163ab0f87..ae393ca1a044b96e25c75c554821b1fc34b9d874 100644 (file)
@@ -75,10 +75,10 @@ Dump a tar archive of a committed tag set using 'git archive'. See
 
 .. option:: checkout [-f|--force]
 
 
 .. option:: checkout [-f|--force]
 
-Update the notmuch database from Git.
+Update the notmuch database from git.
 
 This is mainly useful to discard your changes in notmuch relative
 
 This is mainly useful to discard your changes in notmuch relative
-to Git.
+to git.
 
    .. describe:: [-f|--force]
 
 
    .. describe:: [-f|--force]
 
@@ -101,7 +101,7 @@ upstreams.
 
 .. option:: commit [-f|--force] [message]
 
 
 .. option:: commit [-f|--force] [message]
 
-Commit prefix-matching tags from the notmuch database to Git.
+Commit prefix-matching tags from the notmuch database to git.
 
    .. describe:: message
 
 
    .. describe:: message
 
@@ -166,7 +166,7 @@ Merge changes from 'reference' into HEAD and load the result into notmuch.
 Pull (merge) remote repository changes to notmuch.
 
 **pull** is equivalent to **fetch** followed by **merge**.  We use the
 Pull (merge) remote repository changes to notmuch.
 
 **pull** is equivalent to **fetch** followed by **merge**.  We use the
-Git-configured repository for your current branch
+git-configured repository for your current branch
 (`branch.<name>.repository`, likely `origin`, and `branch.<name>.merge`,
 likely `master` or `main`).
 
 (`branch.<name>.repository`, likely `origin`, and `branch.<name>.merge`,
 likely `master` or `main`).
 
@@ -184,7 +184,7 @@ likely `master` or `main`).
 
 .. option:: push [repository] [refspec]
 
 
 .. option:: push [repository] [refspec]
 
-Push the local `notmuch git` Git state to a remote repository.
+Push the local `notmuch git` git state to a remote repository.
 
     .. describe::  repository
 
 
     .. describe::  repository
 
@@ -210,13 +210,13 @@ where n is a single character representing notmuch database status
 
    .. describe:: A
 
 
    .. describe:: A
 
-   Tag is present in notmuch database, but not committed to nmbug
-   (equivalently, tag has been deleted in nmbug repo, e.g. by a
+   Tag is present in notmuch database, but not committed to git
+   (equivalently, tag has been deleted in git repo, e.g. by a
    pull, but not restored to notmuch database).
 
    .. describe:: D
 
    pull, but not restored to notmuch database).
 
    .. describe:: D
 
-   Tag is present in nmbug repo, but not restored to notmuch
+   Tag is present in git repo, but not restored to notmuch
    database (equivalently, tag has been deleted in notmuch).
 
    .. describe:: U
    database (equivalently, tag has been deleted in notmuch).
 
    .. describe:: U
@@ -229,11 +229,11 @@ run to update this.
 
    .. describe:: a
 
 
    .. describe:: a
 
-   Tag is present in upstream, but not in the local Git branch.
+   Tag is present in upstream, but not in the local git branch.
 
    .. describe:: d
 
 
    .. describe:: d
 
-   Tag is present in local Git branch, but not upstream.
+   Tag is present in local git branch, but not upstream.
 
 .. _format:
 
 
 .. _format:
 
@@ -325,11 +325,11 @@ If it is unset, 'default' is assumed.
 
 .. envvar:: NOTMUCH_GIT_DIR
 
 
 .. envvar:: NOTMUCH_GIT_DIR
 
-   Default location of git repository. Overriden by :option:`--git-dir`.
+   Default location of git repository. Overridden by :option:`--git-dir`.
 
 .. envvar:: NOTMUCH_GIT_PREFIX
 
 
 .. envvar:: NOTMUCH_GIT_PREFIX
 
-   Default tag prefix (filter). Overriden by :option:`--tag-prefix`.
+   Default tag prefix (filter). Overridden by :option:`--tag-prefix`.
 
 SEE ALSO
 ========
 
 SEE ALSO
 ========
index ad305efdb6a07a2ff2635c618da21fcce2c9ad9d..b87737ea3bd0ae4b2ba0ec10a0a7014cc971ff2b 100644 (file)
@@ -43,7 +43,7 @@ Supported options for **search** include
 
 .. option:: --output=(summary|threads|messages|files|tags)
 
 
 .. option:: --output=(summary|threads|messages|files|tags)
 
-   summary
+   summary (default)
      Output a summary of each thread with any message matching the
      search terms. The summary includes the thread ID, date, the
      number of messages in the thread (both the number matched and
      Output a summary of each thread with any message matching the
      search terms. The summary includes the thread ID, date, the
      number of messages in the thread (both the number matched and
index 2c0a0de6ad1679ccc2d2596acd3c8936a968e49c..c13d94de024400fa2ca9642424ab6ec03284ad56 100644 (file)
@@ -130,6 +130,15 @@ Supported options for **show** include
    By default, results will be displayed in reverse chronological
    order, (that is, the newest results will be displayed first).
 
    By default, results will be displayed in reverse chronological
    order, (that is, the newest results will be displayed first).
 
+.. option:: --offset=[-]N
+
+   Skip displaying the first N results. With the leading '-', start
+   at the Nth result from the end.
+
+.. option:: --limit=N
+
+   Limit the number of displayed results to N.
+
 .. option:: --verify
 
    Compute and report the validity of any MIME cryptographic
 .. option:: --verify
 
    Compute and report the validity of any MIME cryptographic
index 422154c7a3303cc5987b4fe3af3691d3c77371a1..858ff6850fd3dc6fb6c978e0fdac30f1adafbe30 100644 (file)
@@ -274,7 +274,7 @@ EXAMPLES
 
     Matches any word starting with "prelim", inside a message subject.
 
 
     Matches any word starting with "prelim", inside a message subject.
 
-``(subject (starts-wih quick) "brown fox")``
+``(subject (starts-with quick) "brown fox")``
 
     Match messages whose subject contains "quick brown fox", but also
     "brown fox quicksand".
 
     Match messages whose subject contains "quick brown fox", but also
     "brown fox quicksand".
@@ -336,7 +336,7 @@ user defined fields is permitted within a macro.
 NOTES
 =====
 
 NOTES
 =====
 
-.. [#macro-details] Technically macros impliment lazy evaluation and
+.. [#macro-details] Technically macros implement lazy evaluation and
                     lexical scope. There is one top level scope
                     containing all macro definitions, but all
                     parameter definitions are local to a given macro.
                     lexical scope. There is one top level scope
                     containing all macro definitions, but all
                     parameter definitions are local to a given macro.
@@ -347,10 +347,10 @@ NOTES
 
 .. [#aka-bool] a.k.a. boolean prefixes
 
 
 .. [#aka-bool] a.k.a. boolean prefixes
 
-.. [#not-phrase] Due to the implemention of phrase fields in Xapian,
+.. [#not-phrase] Due to the implementation of phrase fields in Xapian,
                  regex queries could only match individual words.
 
                  regex queries could only match individual words.
 
-.. [#not-body] Due the the way ``body`` is implemented in notmuch,
+.. [#not-body] Due to the way ``body`` is implemented in notmuch,
                this modifier is not supported in the ``body`` field.
 
 .. [#not-path] Due to the way recursive ``path`` queries are implemented
                this modifier is not supported in the ``body`` field.
 
 .. [#not-path] Due to the way recursive ``path`` queries are implemented
index 846f5e671cf292b3bf3ddeedde94acd7b99da94f..9c2dab471214e6677d148d1189d0a7c8f333efb6 100644 (file)
@@ -14,7 +14,7 @@ manual to refer to the Emacs interface to Notmuch. When this distinction
 is important, we’ll refer to the Emacs interface as
 *notmuch-emacs*.
 
 is important, we’ll refer to the Emacs interface as
 *notmuch-emacs*.
 
-Notmuch-emacs is highly customizable via the the Emacs customization
+Notmuch-emacs is highly customizable via the Emacs customization
 framework (or just by setting the appropriate variables). We try to
 point out relevant variables in this manual, but in order to avoid
 duplication of information, you can usually find the most detailed
 framework (or just by setting the appropriate variables). We try to
 point out relevant variables in this manual, but in order to avoid
 duplication of information, you can usually find the most detailed
@@ -232,6 +232,10 @@ menu of results that the user can explore further by pressing
 
     Refresh the buffer
 
 
     Refresh the buffer
 
+.. el:define-key:: i
+
+    Toggle whether to show messages with excluded tags in search results.
+
 .. el:define-key:: ?
 
     Display full set of key bindings
 .. el:define-key:: ?
 
     Display full set of key bindings
@@ -316,6 +320,27 @@ pressing RET after positioning the cursor on a hidden part.
 
     :ref:`show-copy`
 
 
     :ref:`show-copy`
 
+.. el:define-key:: +
+                   -
+
+    Add or remove arbitrary tags from the current message.
+
+.. el:define-key:: !
+
+    |docstring::notmuch-show-toggle-elide-non-matching|
+
+.. el:define-key:: ?
+
+    Display full set of key bindings
+
+.. _show-navigation:
+
+Navigation
+----------
+
+The following motion/navigation bindings are also available in
+:ref:`notmuch-tree` and :ref:`notmuch-unthreaded`.
+
 .. el:define-key:: N
 
     Move to next message
 .. el:define-key:: N
 
     Move to next message
@@ -332,18 +357,45 @@ pressing RET after positioning the cursor on a hidden part.
 
     Move to previous matching message
 
 
     Move to previous matching message
 
-.. el:define-key:: +
-                   -
+Actions
+-------
 
 
-    Add or remove arbitrary tags from the current message.
+The following actions on messages are also available in
+:ref:`notmuch-tree` and :ref:`notmuch-unthreaded`.
 
 
-.. el:define-key:: !
+.. el:define-key:: b
+                   M-x notmuch-show-resend-message
 
 
-    |docstring::notmuch-show-toggle-elide-non-matching|
+   Resend (*bounce*) message.
 
 
-.. el:define-key:: ?
+.. el:define-key:: e
 
 
-    Display full set of key bindings
+   Resume *editing* a message in :ref:`notmuch-message`. This is
+   particularly useful for :ref:`notmuch-emacs-drafts`, but can be
+   used for other messages as well.
+
+.. el:define-key:: f
+                   M-x notmuch-show-forward-message
+
+   Forward (as a MIME attachment) message
+
+.. el:define-key:: r
+                   M-x notmuch-show-reply-to-sender
+
+   Reply to sender (only) using :ref:`notmuch-message`
+
+.. el:define-key:: w
+                   M-x notmuch-show-save-attachments
+
+   |docstring::notmuch-show-save-attachments|
+
+.. el:define-key:: R
+                   M-x notmuch-show-reply
+
+   |docstring::notmuch-show-reply|
+
+Configuration
+-------------
 
 Display of messages can be controlled by the following variables; see also :ref:`show-large`.
 
 
 Display of messages can be controlled by the following variables; see also :ref:`show-large`.
 
@@ -493,7 +545,7 @@ in :ref:`notmuch-search`.
 Dealing with duplicates
 -----------------------
 
 Dealing with duplicates
 -----------------------
 
-If there are are multiple files with the same :mailheader:`Message-ID`
+If there are multiple files with the same :mailheader:`Message-ID`
 (see :any:`duplicate-files`), then :any:`notmuch-show` displays the
 number of duplicates and identifies the current duplicate. In the
 following example duplicate 3 of 5 is displayed.
 (see :any:`duplicate-files`), then :any:`notmuch-show` displays the
 number of duplicates and identifies the current duplicate. In the
 following example duplicate 3 of 5 is displayed.
@@ -518,7 +570,7 @@ notmuch-tree
 ``notmuch-tree-mode`` displays the results of a "notmuch tree" of your
 email archives. Each line in the buffer represents a single
 message giving the relative date, the author, subject, and any
 ``notmuch-tree-mode`` displays the results of a "notmuch tree" of your
 email archives. Each line in the buffer represents a single
 message giving the relative date, the author, subject, and any
-tags.
+tags. For common navigation commands see :ref:`show-navigation`.
 
 .. el:define-key:: c
 
 
 .. el:define-key:: c
 
@@ -528,22 +580,6 @@ tags.
 
    Displays that message.
 
 
    Displays that message.
 
-.. el:define-key:: N
-
-    Move to next message
-
-.. el:define-key:: P
-
-    Move to previous message
-
-.. el:define-key:: n
-
-    Move to next matching message
-
-.. el:define-key:: p
-
-    Move to previous matching message
-
 .. el:define-key:: o
    M-x notmuch-tree-toggle-order
 
 .. el:define-key:: o
    M-x notmuch-tree-toggle-order
 
@@ -559,6 +595,9 @@ tags.
 
    Filter the current search results based on an additional tag
 
 
    Filter the current search results based on an additional tag
 
+.. el:define-key:: i
+
+    Toggle whether to show messages with excluded tags in search results.
 
 .. el:define-key:: g
    =
 
 .. el:define-key:: g
    =
@@ -606,6 +645,45 @@ can be controlled by the variable ``notmuch-search-oldest-first``.
    See also :el:defcustom:`notmuch-search-result-format` and
    :el:defcustom:`notmuch-unthreaded-result-format`.
 
    See also :el:defcustom:`notmuch-search-result-format` and
    :el:defcustom:`notmuch-unthreaded-result-format`.
 
+.. _notmuch-tree-outline:
+
+notmuch-tree-outline
+--------------------
+
+When this mode is set, each thread and subthread in the results
+list is treated as a foldable section, with its first message as
+its header.
+
+The mode just makes available in the tree buffer all the
+keybindings in info:emacs#Outline_Mode, and binds the following
+additional keys:
+
+.. el:define-key:: <tab>
+
+   Cycle visibility state of the current message's tree.
+
+.. el:define-key:: <M-tab>
+
+   Cycle visibility state of all trees in the buffer.
+
+The behaviour of this minor mode is affected by the following
+customizable variables:
+
+.. el:defcustom:: notmuch-tree-outline-enabled
+
+   |docstring::notmuch-tree-outline-enabled|
+
+.. el:defcustom:: notmuch-tree-outline-visibility
+
+   |docstring::notmuch-tree-outline-visibility|
+
+.. el:defcustom:: notmuch-tree-outline-auto-close
+
+   |docstring::notmuch-tree-outline-auto-close|
+
+.. el:defcustom:: notmuch-tree-outline-open-on-next
+
+   |docstring::notmuch-tree-outline-open-on-next|
 
 .. _notmuch-unthreaded:
 
 
 .. _notmuch-unthreaded:
 
@@ -625,6 +703,56 @@ Keybindings are the same as :any:`notmuch-tree`.
    See also :el:defcustom:`notmuch-search-result-format` and
    :el:defcustom:`notmuch-tree-result-format`.
 
    See also :el:defcustom:`notmuch-search-result-format` and
    :el:defcustom:`notmuch-tree-result-format`.
 
+.. _notmuch-message:
+
+notmuch-message
+===============
+
+Notmuch uses a slighly customized version of the standard emacs email
+composition mode info:message.
+
+.. _notmuch-emacs-drafts:
+
+Drafts
+------
+
+.. el:define-key:: C-x C-p
+                   M-x notmuch-draft-postpone
+
+   Postpone (save and bury) a draft.
+
+.. el:define-key:: C-x C-s
+                   M-x notmuch-draft-save
+
+   Save a draft and continue editing.
+
+.. _notmuch-emacs-replies:
+
+.. _notmuch-emacs-fcc:
+
+Fcc
+---
+
+.. el:defcustom:: notmuch-fcc-dirs
+
+   |docstring::notmuch-fcc-dirs|
+
+.. el:defcustom:: notmuch-maildir-use-notmuch-insert
+
+   |docstring::notmuch-maildir-use-notmuch-insert|
+
+Replies
+-------
+
+.. el:defcustom:: message-dont-reply-to-names
+
+       When composing mail replies, Emacs's message mode uses the
+       variable :code:`message-dont-reply-to-names` to exclude
+       recipients matching a given collection of regular expressions
+       or satisfying an arbitrary predicate.  Notmuch's MUA inherits
+       this standard mechanism and will honour your customization of
+       this variable.
+
 Global key bindings
 ===================
 
 Global key bindings
 ===================
 
@@ -678,7 +806,7 @@ operations specified in ``notmuch-tagging-keys``; i.e. each
 notmuch-tag-undo
 ----------------
 
 notmuch-tag-undo
 ----------------
 
-Each notmuch buffer supporting tagging operations (i.e buffers in
+Each notmuch buffer supporting tagging operations (i.e. buffers in
 :any:`notmuch-show`, :any:`notmuch-search`, :any:`notmuch-tree`, and
 :any:`notmuch-unthreaded` mode) keeps a local stack of tagging
 operations. These can be undone via :any:`notmuch-tag-undo`. By default
 :any:`notmuch-show`, :any:`notmuch-search`, :any:`notmuch-tree`, and
 :any:`notmuch-unthreaded` mode) keeps a local stack of tagging
 operations. These can be undone via :any:`notmuch-tag-undo`. By default
@@ -724,15 +852,6 @@ Sending Mail
        :code:`compose-mail`.  To use ``notmuch`` for this, customize this
        variable to the symbol :code:`notmuch-user-agent`.
 
        :code:`compose-mail`.  To use ``notmuch`` for this, customize this
        variable to the symbol :code:`notmuch-user-agent`.
 
-.. el:defcustom:: message-dont-reply-to-names
-
-       When composing mail replies, Emacs's message mode uses the
-       variable :code:`message-dont-reply-to-names` to exclude
-       recipients matching a given collection of regular expressions
-       or satisfying an arbitrary predicate.  Notmuch's MUA inherits
-       this standard mechanism and will honour your customization of
-       this variable.
-
 Init File
 ---------
 
 Init File
 ---------
 
index 1a4cdda22b95ffee9434b589f0912700b8c22b02..f756254cb8513ec062ca692299fbb39e4f683fe1 100644 (file)
@@ -153,8 +153,7 @@ matching `notmuch-address-completion-headers-regexp'."
 ;;; Setup
 
 (defun notmuch-address-selection-function (prompt collection initial-input)
 ;;; Setup
 
 (defun notmuch-address-selection-function (prompt collection initial-input)
-  "Call (`completing-read'
-      PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
+  "Default address selection function: delegate to completing read."
   (completing-read
    prompt collection nil nil initial-input 'notmuch-address-history))
 
   (completing-read
    prompt collection nil nil initial-input 'notmuch-address-history))
 
index a521497784ec27e91cf9c7ee905641b3d4459012..254e6407cecefbbd8656321d9283cf63d360afca 100755 (executable)
@@ -41,6 +41,9 @@ CREATE_FRAME=
 ELISP=
 MAILTO=
 HELLO=
 ELISP=
 MAILTO=
 HELLO=
+TO_SEP=
+CC_SEP=
+BCC_SEP=
 
 # Short options compatible with mutt(1).
 while getopts :s:c:b:i:h opt; do
 
 # Short options compatible with mutt(1).
 while getopts :s:c:b:i:h opt; do
@@ -86,13 +89,16 @@ while getopts :s:c:b:i:h opt; do
            ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
            ;;
        --to)
            ELISP="${ELISP} (message-goto-subject) (insert \"${OPTARG}\")"
            ;;
        --to)
-           ELISP="${ELISP} (message-goto-to) (insert \"${OPTARG}, \")"
+           ELISP="${ELISP} (message-goto-to) (insert \"${TO_SEP}${OPTARG}\")"
+           TO_SEP=", "
            ;;
        --cc|c)
            ;;
        --cc|c)
-           ELISP="${ELISP} (message-goto-cc) (insert \"${OPTARG}, \")"
+           ELISP="${ELISP} (message-goto-cc) (insert \"${CC_SEP}${OPTARG}\")"
+           CC_SEP=", "
            ;;
        --bcc|b)
            ;;
        --bcc|b)
-           ELISP="${ELISP} (message-goto-bcc) (insert \"${OPTARG}, \")"
+           ELISP="${ELISP} (message-goto-bcc) (insert \"${BCC_SEP}${OPTARG}\")"
+           BCC_SEP=", "
            ;;
        --body|i)
            ELISP="${ELISP} (message-goto-body) (insert-file \"${OPTARG}\")"
            ;;
        --body|i)
            ELISP="${ELISP} (message-goto-body) (insert-file \"${OPTARG}\")"
index 4662e704f5c253ca99d1eb47456571d41faf3392..67c805a8549fd3f6d7eb0e9cff1e9f37b1c9be36 100644 (file)
 (declare-function notmuch-poll "notmuch-lib" ())
 (declare-function notmuch-tree "notmuch-tree"
                  (&optional query query-context target buffer-name
 (declare-function notmuch-poll "notmuch-lib" ())
 (declare-function notmuch-tree "notmuch-tree"
                  (&optional query query-context target buffer-name
-                            open-target unthreaded parent-buffer oldest-first))
+                            open-target unthreaded parent-buffer
+                            oldest-first hide-excluded))
 (declare-function notmuch-unthreaded "notmuch-tree"
                  (&optional query query-context target buffer-name
 (declare-function notmuch-unthreaded "notmuch-tree"
                  (&optional query query-context target buffer-name
-                            open-target))
+                            open-target oldest-first hide-excluded))
 
 
 ;;; Options
 
 
 ;;; Options
@@ -115,6 +116,12 @@ searches so they still work in customize."
                                    (const :tag "Default" nil)
                                    (const :tag "Oldest-first" oldest-first)
                                    (const :tag "Newest-first" newest-first)))
                                    (const :tag "Default" nil)
                                    (const :tag "Oldest-first" oldest-first)
                                    (const :tag "Newest-first" newest-first)))
+                    (group :format "%v" :inline t
+                           (const :format "" :excluded)
+                           (choice :tag " Hide Excluded"
+                                   (const :tag "Default" nil)
+                                   (const :tag "Hide" hide)
+                                   (const :tag "Show" show)))
                     (group :format "%v" :inline t
                            (const :format "" :search-type)
                            (choice :tag " Search Type"
                     (group :format "%v" :inline t
                            (const :format "" :search-type)
                            (choice :tag " Search Type"
@@ -143,9 +150,13 @@ a plist. Supported properties are
   :sort-order      Specify the sort order to be used for the search.
                    Possible values are `oldest-first', `newest-first'
                    or nil. Nil means use the default sort order.
   :sort-order      Specify the sort order to be used for the search.
                    Possible values are `oldest-first', `newest-first'
                    or nil. Nil means use the default sort order.
+  :excluded        Whether to show mail with excluded tags in the
+                   search. Possible values are `hide', `show',
+                   or nil. Nil means use the default value of
+                   `notmuch-search-hide-excluded'.
   :search-type     Specify whether to run the search in search-mode,
                    tree mode or unthreaded mode. Set to `tree' to
   :search-type     Specify whether to run the search in search-mode,
                    tree mode or unthreaded mode. Set to `tree' to
-                   specify tree mode, 'unthreaded to specify
+                   specify tree mode, \\='unthreaded to specify
                    unthreaded mode, and set to nil (or anything
                    except tree and unthreaded) to specify search
                    mode.
                    unthreaded mode, and set to nil (or anything
                    except tree and unthreaded) to specify search
                    mode.
@@ -484,19 +495,19 @@ diagonal."
             append (notmuch-hello-reflect-generate-row ncols nrows row list))))
 
 (defun notmuch-hello-widget-search (widget &rest _ignore)
             append (notmuch-hello-reflect-generate-row ncols nrows row list))))
 
 (defun notmuch-hello-widget-search (widget &rest _ignore)
-  (cl-case (widget-get widget :notmuch-search-type)
-   (tree
-    (let ((n (notmuch-search-format-buffer-name (widget-value widget) "tree" t)))
-      (notmuch-tree (widget-get widget :notmuch-search-terms)
-                   nil nil n nil nil nil
-                   (widget-get widget :notmuch-search-oldest-first))))
-   (unthreaded
-    (let ((n (notmuch-search-format-buffer-name (widget-value widget)
-                                               "unthreaded" t)))
-      (notmuch-unthreaded (widget-get widget :notmuch-search-terms) nil nil n)))
-   (t
-    (notmuch-search (widget-get widget :notmuch-search-terms)
-                   (widget-get widget :notmuch-search-oldest-first)))))
+  (let ((search-terms (widget-get widget :notmuch-search-terms))
+       (oldest-first (widget-get widget :notmuch-search-oldest-first))
+       (exclude (widget-get widget :notmuch-search-hide-excluded)))
+    (cl-case (widget-get widget :notmuch-search-type)
+      (tree
+       (let ((n (notmuch-search-format-buffer-name (widget-value widget) "tree" t)))
+        (notmuch-tree search-terms nil nil n nil nil nil oldest-first exclude)))
+      (unthreaded
+       (let ((n (notmuch-search-format-buffer-name (widget-value widget)
+                                                  "unthreaded" t)))
+        (notmuch-unthreaded search-terms nil nil n nil oldest-first exclude)))
+      (t
+       (notmuch-search search-terms oldest-first exclude)))))
 
 (defun notmuch-saved-search-count (search)
   (car (notmuch--process-lines notmuch-command "count" search)))
 
 (defun notmuch-saved-search-count (search)
   (car (notmuch--process-lines notmuch-command "count" search)))
@@ -643,6 +654,10 @@ with `notmuch-hello-query-counts'."
                                     (newest-first nil)
                                     (oldest-first t)
                                     (otherwise notmuch-search-oldest-first)))
                                     (newest-first nil)
                                     (oldest-first t)
                                     (otherwise notmuch-search-oldest-first)))
+                    (exclude (cl-case (plist-get elem :excluded)
+                               (hide t)
+                               (show nil)
+                               (otherwise notmuch-search-hide-excluded)))
                     (search-type (plist-get elem :search-type))
                     (msg-count (plist-get elem :count)))
                (widget-insert (format "%8s "
                     (search-type (plist-get elem :search-type))
                     (msg-count (plist-get elem :count)))
                (widget-insert (format "%8s "
@@ -652,6 +667,7 @@ with `notmuch-hello-query-counts'."
                               :notmuch-search-terms query
                               :notmuch-search-oldest-first oldest-first
                               :notmuch-search-type search-type
                               :notmuch-search-terms query
                               :notmuch-search-oldest-first oldest-first
                               :notmuch-search-type search-type
+                              :notmuch-search-hide-excluded exclude
                               name)
                (setq column-indent
                      (1+ (max 0 (- column-width (length name)))))))
                               name)
                (setq column-indent
                      (1+ (max 0 (- column-width (length name)))))))
@@ -694,11 +710,7 @@ with `notmuch-hello-query-counts'."
            ;; configuration change, and this is not a new window)
            (setq do-refresh t)))))
     (when (and do-refresh notmuch-hello-auto-refresh)
            ;; configuration change, and this is not a new window)
            (setq do-refresh t)))))
     (when (and do-refresh notmuch-hello-auto-refresh)
-      ;; Refresh hello as soon as we get back to redisplay.  On Emacs
-      ;; 24, we can't do it right here because something in this
-      ;; hook's call stack overrides hello's point placement.
-      ;; FIXME And on Emacs releases that we still support?
-      (run-at-time nil nil #'notmuch-hello t))
+      (notmuch-hello t))
     (unless hello-buf
       ;; Clean up hook
       (remove-hook 'window-configuration-change-hook
     (unless hello-buf
       ;; Clean up hook
       (remove-hook 'window-configuration-change-hook
@@ -717,7 +729,8 @@ with `notmuch-hello-query-counts'."
   "Keymap for \"notmuch hello\" buffers.")
 
 (define-derived-mode notmuch-hello-mode fundamental-mode "notmuch-hello"
   "Keymap for \"notmuch hello\" buffers.")
 
 (define-derived-mode notmuch-hello-mode fundamental-mode "notmuch-hello"
-  "Major mode for convenient notmuch navigation. This is your entry portal into notmuch.
+  "Major mode for convenient notmuch navigation. This is your entry
+portal into notmuch.
 
 Saved searches are \"bookmarks\" for arbitrary queries. Hit RET
 or click on a saved search to view matching threads. Edit saved
 
 Saved searches are \"bookmarks\" for arbitrary queries. Hit RET
 or click on a saved search to view matching threads. Edit saved
@@ -747,7 +760,9 @@ The screen may be customized via `\\[customize]'.
 Complete list of currently available key bindings:
 
 \\{notmuch-hello-mode-map}"
 Complete list of currently available key bindings:
 
 \\{notmuch-hello-mode-map}"
-  (setq notmuch-buffer-refresh-function #'notmuch-hello-update))
+  (setq notmuch-buffer-refresh-function #'notmuch-hello-update)
+  (when (boundp 'untrusted-content)
+    (setq untrusted-content t)))
 
 ;;; Inserters
 
 
 ;;; Inserters
 
@@ -853,7 +868,8 @@ Complete list of currently available key bindings:
        (widget-create 'notmuch-search-item :value search :size width)))))
 
 (defun notmuch-hello-insert-searches (title query-list &rest options)
        (widget-create 'notmuch-search-item :value search :size width)))))
 
 (defun notmuch-hello-insert-searches (title query-list &rest options)
-  "Insert a section with TITLE showing a list of buttons made from QUERY-LIST.
+  "Insert a section with TITLE showing a list of buttons made from
+QUERY-LIST.
 
 QUERY-LIST should ideally be a plist but for backwards
 compatibility other forms are also accepted (see
 
 QUERY-LIST should ideally be a plist but for backwards
 compatibility other forms are also accepted (see
@@ -867,13 +883,16 @@ Supports the following entries in OPTIONS as a plist:
 :show-empty-searches - show buttons with no matching messages
 :hide-if-empty - hide if no buttons would be shown
    (only makes sense without :show-empty-searches)
 :show-empty-searches - show buttons with no matching messages
 :hide-if-empty - hide if no buttons would be shown
    (only makes sense without :show-empty-searches)
-:filter - This can be a function that takes the search query as its argument and
-   returns a filter to be used in conjunction with the query for that search or nil
-   to hide the element. This can also be a string that is used as a combined with
-   each query using \"and\".
-:filter-count - Separate filter to generate the count displayed each search. Accepts
-   the same values as :filter. If :filter and :filter-count are specified, this
-   will be used instead of :filter, not in conjunction with it."
+:filter - This can be a function that takes the search query as
+   its argument and returns a filter to be used in conjunction
+   with the query for that search or nil to hide the
+   element. This can also be a string that is used as a combined
+   with each query using \"and\".
+:filter-count - Separate filter to generate the count displayed
+   each search. Accepts the same values as :filter. If :filter
+   and :filter-count are specified, this will be used instead of
+   :filter, not in conjunction with it."
+
   (widget-insert title ": ")
   (when (and notmuch-hello-first-run (plist-get options :initially-hidden))
     (add-to-list 'notmuch-hello-hidden-sections title))
   (widget-insert title ": ")
   (when (and notmuch-hello-first-run (plist-get options :initially-hidden))
     (add-to-list 'notmuch-hello-hidden-sections title))
index 6a2769282ec666190b1a7cdeea8364782389977f..3161ed9526d52d5b8de9a9d08eb3bef626eb38ec 100644 (file)
@@ -50,15 +50,21 @@ fast way to jump to a saved search from anywhere in Notmuch."
                 (cl-case (plist-get saved-search :sort-order)
                   (newest-first nil)
                   (oldest-first t)
                 (cl-case (plist-get saved-search :sort-order)
                   (newest-first nil)
                   (oldest-first t)
-                  (otherwise (default-value 'notmuch-search-oldest-first)))))
+                  (otherwise (default-value 'notmuch-search-oldest-first))))
+               (exclude (cl-case (plist-get saved-search :excluded)
+                          (hide t)
+                          (show nil)
+                          (otherwise notmuch-search-hide-excluded))))
            (push (list key name
                        (cond
                         ((eq (plist-get saved-search :search-type) 'tree)
            (push (list key name
                        (cond
                         ((eq (plist-get saved-search :search-type) 'tree)
-                         (lambda () (notmuch-tree query)))
+                         (lambda () (notmuch-tree query nil nil nil nil nil nil
+                                             oldest-first exclude)))
                         ((eq (plist-get saved-search :search-type) 'unthreaded)
                         ((eq (plist-get saved-search :search-type) 'unthreaded)
-                         (lambda () (notmuch-unthreaded query)))
+                         (lambda () (notmuch-unthreaded query nil nil nil nil
+                                                   oldest-first exclude)))
                         (t
                         (t
-                         (lambda () (notmuch-search query oldest-first)))))
+                         (lambda () (notmuch-search query oldest-first exclude)))))
                  action-map)))))
     (setq action-map (nreverse action-map))
     (if action-map
                  action-map)))))
     (setq action-map (nreverse action-map))
     (if action-map
index 84ba8c5e56de2aa151682bea74e30a4f3808b2e3..bf9c4a534a24f5d5c088857ce420600604259326 100644 (file)
@@ -105,6 +105,16 @@ search."
   :group 'notmuch-search)
 (make-variable-buffer-local 'notmuch-search-oldest-first)
 
   :group 'notmuch-search)
 (make-variable-buffer-local 'notmuch-search-oldest-first)
 
+(defcustom notmuch-search-hide-excluded t
+  "Hide mail tagged with a excluded tag.
+
+Excluded tags are defined in the users configuration file under
+the search section. When this variable is true, any mail with
+such a tag will not be shown in the search output."
+  :type 'boolean
+  :group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-hide-excluded)
+
 (defcustom notmuch-poll-script nil
   "[Deprecated] Command to run to incorporate new mail into the notmuch database.
 
 (defcustom notmuch-poll-script nil
   "[Deprecated] Command to run to incorporate new mail into the notmuch database.
 
@@ -414,9 +424,9 @@ This is similar to `describe-function' for the current major
 mode, but bindings tables are shown with documentation strings
 rather than command names.  By default, this uses the first line
 of each command's documentation string.  A command can override
 mode, but bindings tables are shown with documentation strings
 rather than command names.  By default, this uses the first line
 of each command's documentation string.  A command can override
-this by setting the 'notmuch-doc property of its command symbol.
+this by setting the \\='notmuch-doc property of its command symbol.
 A command that supports a prefix argument can explicitly document
 A command that supports a prefix argument can explicitly document
-its prefixed behavior by setting the 'notmuch-prefix-doc property
+its prefixed behavior by setting the \\='notmuch-prefix-doc property
 of its command symbol."
   (interactive)
   (let ((doc (substitute-command-keys
 of its command symbol."
   (interactive)
   (let ((doc (substitute-command-keys
@@ -703,6 +713,7 @@ current buffer, if possible."
          (when (mm-inlinable-p handle)
            (set-buffer display-buffer)
            (mm-display-part handle)
          (when (mm-inlinable-p handle)
            (set-buffer display-buffer)
            (mm-display-part handle)
+           (plist-put part :undisplayer (mm-handle-undisplayer handle))
            t))))))
 
 ;;; Generic Utilities
            t))))))
 
 ;;; Generic Utilities
@@ -725,7 +736,7 @@ single element face list."
     (list face)))
 
 (defun notmuch-apply-face (object face &optional below start end)
     (list face)))
 
 (defun notmuch-apply-face (object face &optional below start end)
-  "Combine FACE into the 'face text property of OBJECT between START and END.
+  "Combine FACE into the \\='face text property of OBJECT between START and END.
 
 This function combines FACE with any existing faces between START
 and END in OBJECT.  Attributes specified by FACE take precedence
 
 This function combines FACE with any existing faces between START
 and END in OBJECT.  Attributes specified by FACE take precedence
index 5102078849d629a2da37bac13c9a3548153ff996..c7b403cfe68f805baf186f64be64bd75f9cf3101 100644 (file)
@@ -145,14 +145,16 @@ Otherwise set it according to `notmuch-fcc-dirs'."
 
 (defmacro with-temporary-notmuch-message-buffer (&rest body)
   "Set-up a temporary copy of the current message-mode buffer."
 
 (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*")
-       (message-clone-locals buf) ;; for message-encoded-mail-cache
-       (erase-buffer)
-       (insert-buffer-substring buf)
-       ,@body)))
+  `(save-restriction
+     (widen)
+     (let ((case-fold-search t)
+          (buf (current-buffer))
+          (mml-externalize-attachments message-fcc-externalize-attachments))
+       (with-current-buffer (get-buffer-create " *message temp*")
+        (message-clone-locals buf) ;; for message-encoded-mail-cache
+        (erase-buffer)
+        (insert-buffer-substring buf)
+        ,@body))))
 
 (defun notmuch-maildir-setup-message-for-saving ()
   "Setup message for saving.
 
 (defun notmuch-maildir-setup-message-for-saving ()
   "Setup message for saving.
index ac878a619d07e7c4947f1c359b9d6825580516b6..74c62aafd28d69eb5de5135ee4f9f53c481d72e4 100644 (file)
@@ -142,6 +142,16 @@ to `notmuch-mua-send-hook'."
   :type 'regexp
   :group 'notmuch-send)
 
   :type 'regexp
   :group 'notmuch-send)
 
+(defcustom notmuch-mua-subject-regexp
+  "[[:blank:]]*$"
+  "Message subject indicating that something may be amiss.
+By default, this checks for empty subject lines.
+
+This is not used unless `notmuch-mua-subject-check' is added to
+`notmuch-mua-send-hook'."
+  :type 'regexp
+  :group 'notmuch-send)
+
 ;;; Various functions
 
 (defun notmuch-mua-attachment-check ()
 ;;; Various functions
 
 (defun notmuch-mua-attachment-check ()
@@ -179,6 +189,19 @@ Typically this is added to `notmuch-mua-send-hook'."
     ;; ...signal an error.
     (error "Missing attachment")))
 
     ;; ...signal an error.
     (error "Missing attachment")))
 
+(defun notmuch-mua-subject-check ()
+  "Signal an error if the subject seems amiss.
+More precisely, if the subject conforms to
+`notmuch-mua-subject-regexp'.
+
+Typically this is added to `notmuch-mua-send-hook'."
+  (or (save-excursion
+        (message-goto-subject)
+        (message-beginning-of-header t)
+        (not (looking-at-p notmuch-mua-subject-regexp)))
+      (y-or-n-p "Subject may be erroneous – is that okay?")
+      (error "Erroneous subject")))
+
 (defun notmuch-mua-get-switch-function ()
   "Get a switch function according to `notmuch-mua-compose-in'."
   (pcase notmuch-mua-compose-in
 (defun notmuch-mua-get-switch-function ()
   "Get a switch function according to `notmuch-mua-compose-in'."
   (pcase notmuch-mua-compose-in
@@ -356,7 +379,9 @@ Typically this is added to `notmuch-mua-send-hook'."
 
 (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
   "Notmuch message composition mode. Mostly like `message-mode'."
 
 (define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
   "Notmuch message composition mode. Mostly like `message-mode'."
-  (notmuch-address-setup))
+  (notmuch-address-setup)
+  (when (boundp 'untrusted-content)
+    (setq untrusted-content t)))
 
 (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
 
 
 (put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
 
@@ -401,9 +426,10 @@ instead of `message-mode' and SWITCH-FUNCTION is mandatory."
               (addr (and good-tokens (mapconcat #'identity good-tokens ", "))))
          (message-replace-header header addr))))))
 
               (addr (and good-tokens (mapconcat #'identity good-tokens ", "))))
          (message-replace-header header addr))))))
 
+;;;###autoload
 (defun notmuch-mua-mail (&optional to subject other-headers _continue
                                   switch-function yank-action send-actions
 (defun notmuch-mua-mail (&optional to subject other-headers _continue
                                   switch-function yank-action send-actions
-                                  return-action &rest ignored)
+                                  return-action &rest _ignored)
   "Invoke the notmuch mail composition window.
 
 The position of point when the function returns differs depending
   "Invoke the notmuch mail composition window.
 
 The position of point when the function returns differs depending
@@ -416,11 +442,6 @@ moved to the \"To:\" header."
     (let ((user-agent (funcall notmuch-mua-user-agent-function)))
       (unless (string-empty-p user-agent)
        (push (cons 'User-Agent user-agent) other-headers))))
     (let ((user-agent (funcall notmuch-mua-user-agent-function)))
       (unless (string-empty-p user-agent)
        (push (cons 'User-Agent user-agent) other-headers))))
-  (unless (assq 'From other-headers)
-    (push (cons 'From (message-make-from
-                      (notmuch-user-name)
-                      (notmuch-user-primary-email)))
-         other-headers))
   (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
                             (or switch-function
                                 (notmuch-mua-get-switch-function)))
   (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
                             (or switch-function
                                 (notmuch-mua-get-switch-function)))
@@ -439,6 +460,11 @@ moved to the \"To:\" header."
        ;; Cause `message-setup-1' to do things relevant for mail,
        ;; such as observe `message-default-mail-headers'.
        (message-this-is-mail t))
        ;; Cause `message-setup-1' to do things relevant for mail,
        ;; such as observe `message-default-mail-headers'.
        (message-this-is-mail t))
+    (unless (assq 'From headers)
+      (push (cons 'From (message-make-from
+                        (notmuch-user-name)
+                        (notmuch-user-primary-email)))
+           headers))
     (message-setup-1 headers yank-action send-actions return-action))
   (notmuch-fcc-header-setup)
   (notmuch-mua--remove-dont-reply-to-names)
     (message-setup-1 headers yank-action send-actions return-action))
   (notmuch-fcc-header-setup)
   (notmuch-mua--remove-dont-reply-to-names)
@@ -622,20 +648,24 @@ unencrypted.  Really send? "))))
          (message-send-and-exit arg)
        (message-send arg)))))
 
          (message-send-and-exit arg)
        (message-send arg)))))
 
+;;;###autoload
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
   (notmuch-mua-send-common arg t))
 
 (defun notmuch-mua-send-and-exit (&optional arg)
   (interactive "P")
   (notmuch-mua-send-common arg t))
 
+;;;###autoload
 (defun notmuch-mua-send (&optional arg)
   (interactive "P")
   (notmuch-mua-send-common arg))
 
 (defun notmuch-mua-send (&optional arg)
   (interactive "P")
   (notmuch-mua-send-common arg))
 
+;;;###autoload
 (defun notmuch-mua-kill-buffer ()
   (interactive)
   (message-kill-buffer))
 
 ;;; _
 
 (defun notmuch-mua-kill-buffer ()
   (interactive)
   (message-kill-buffer))
 
 ;;; _
 
+;;;###autoload
 (define-mail-user-agent 'notmuch-user-agent
   'notmuch-mua-mail
   'notmuch-mua-send-and-exit
 (define-mail-user-agent 'notmuch-user-agent
   'notmuch-mua-mail
   'notmuch-mua-send-and-exit
index f04b07c2e51b28e34f03469b88205d279c45a587..710c60e15ec12c7b3f5d6ca02d869d5be45f03d6 100644 (file)
@@ -35,7 +35,7 @@ complete S-expression from the input.  However, it extends this
 with an additional function that requires the next value in the
 input to be a list and descends into it, allowing its elements to
 be read one at a time or further descended into.  Both functions
 with an additional function that requires the next value in the
 input to be a list and descends into it, allowing its elements to
 be read one at a time or further descended into.  Both functions
-can return 'retry to indicate that not enough input is available.
+can return \\='retry to indicate that not enough input is available.
 
 The parser always consumes input from point in the current
 buffer.  Hence, the caller is allowed to delete any data before
 
 The parser always consumes input from point in the current
 buffer.  Hence, the caller is allowed to delete any data before
@@ -52,10 +52,10 @@ point and may resynchronize after an error by moving point."
 (defun notmuch-sexp-read (sp)
   "Consume and return the value at point in the current buffer.
 
 (defun notmuch-sexp-read (sp)
   "Consume and return the value at point in the current buffer.
 
-Returns 'retry if there is insufficient input to parse a complete
+Returns \\='retry if there is insufficient input to parse a complete
 value (though it may still move point over whitespace).  If the
 parser is currently inside a list and the next token ends the
 value (though it may still move point over whitespace).  If the
 parser is currently inside a list and the next token ends the
-list, this moves point just past the terminator and returns 'end.
+list, this moves point just past the terminator and returns \\='end.
 Otherwise, this moves point to just past the end of the value and
 returns the value."
   (skip-chars-forward " \n\r\t")
 Otherwise, this moves point to just past the end of the value and
 returns the value."
   (skip-chars-forward " \n\r\t")
@@ -125,7 +125,7 @@ returns the value."
 (defun notmuch-sexp-begin-list (sp)
   "Parse the beginning of a list value and enter the list.
 
 (defun notmuch-sexp-begin-list (sp)
   "Parse the beginning of a list value and enter the list.
 
-Returns 'retry if there is insufficient input to parse the
+Returns \\='retry if there is insufficient input to parse the
 beginning of the list.  If this is able to parse the beginning of
 a list, it moves point past the token that opens the list and
 returns t.  Later calls to `notmuch-sexp-read' will return the
 beginning of the list.  If this is able to parse the beginning of
 a list, it moves point past the token that opens the list and
 returns t.  Later calls to `notmuch-sexp-read' will return the
index 85fa1f218cfae1111cdb3e053c49cd4e05540b38..8d9f1b0830319c5d2bb2262029334d89b5174a10 100644 (file)
 ;;; Utility functions
 
 (defun notmuch-print-run-evince (file)
 ;;; Utility functions
 
 (defun notmuch-print-run-evince (file)
-  "View FILE using 'evince'."
+  "View FILE using `evince'."
   (start-process "evince" nil "evince" file))
 
 (defun notmuch-print-run-muttprint (&optional output)
   (start-process "evince" nil "evince" file))
 
 (defun notmuch-print-run-muttprint (&optional output)
-  "Pass the contents of the current buffer to 'muttprint'.
+  "Pass the contents of the current buffer to `muttprint'.
 
 Optional OUTPUT allows passing a list of flags to muttprint."
   (apply #'notmuch--call-process-region (point-min) (point-max)
 
 Optional OUTPUT allows passing a list of flags to muttprint."
   (apply #'notmuch--call-process-region (point-min) (point-max)
index ec998ede8dad49ebc1421f7eb839dd24da70cde7..19b517539255005928f290311272c1cf4bcb4c56 100644 (file)
@@ -104,7 +104,7 @@ notmuch-show functions such as
 
 Finally, if this variable is set to nil, no header is
 displayed."
 
 Finally, if this variable is set to nil, no header is
 displayed."
-  :type '(choice (const :tag "No header" ni)
+  :type '(choice (const :tag "No header" nil)
                  (const :tag "Subject" t)
                  (string :tag "Format")
                 (function :tag "Function"))
                  (const :tag "Subject" t)
                  (string :tag "Format")
                 (function :tag "Function"))
@@ -452,14 +452,19 @@ operation on the contents of the current buffer."
 (defun notmuch-show-update-tags (tags)
   "Update the displayed tags of the current message."
   (save-excursion
 (defun notmuch-show-update-tags (tags)
   "Update the displayed tags of the current message."
   (save-excursion
-    (goto-char (notmuch-show-message-top))
-    (when (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
-      (let ((inhibit-read-only t))
-       (replace-match (concat "("
-                              (notmuch-tag-format-tags
-                               tags
-                               (notmuch-show-get-prop :orig-tags))
-                              ")"))))))
+    (let ((inhibit-read-only t)
+         (start (notmuch-show-message-top))
+         (depth (notmuch-show-get-prop :depth))
+         (orig-tags (notmuch-show-get-prop :orig-tags))
+         (props (notmuch-show-get-message-properties))
+         (extent (notmuch-show-message-extent)))
+      (goto-char start)
+      (notmuch-show-insert-headerline props depth tags orig-tags)
+      (put-text-property start (1+ start)
+                        :notmuch-message-properties props)
+      (put-text-property (car extent) (cdr extent) :notmuch-message-extent extent)
+      ;; delete original headerline, but do not save to kill ring
+      (delete-region (point) (1+ (line-end-position))))))
 
 (defun notmuch-clean-address (address)
   "Try to clean a single email ADDRESS for display. Return a cons
 
 (defun notmuch-clean-address (address)
   "Try to clean a single email ADDRESS for display. Return a cons
@@ -530,11 +535,17 @@ Return unchanged ADDRESS if parsing fails."
          (plist-put msg :height height)
          height))))
 
          (plist-put msg :height height)
          height))))
 
-(defun notmuch-show-insert-headerline (headers date tags depth duplicate file-count)
+(defun notmuch-show-insert-headerline (msg-plist depth tags &optional orig-tags)
   "Insert a notmuch style headerline based on HEADERS for a
 message at DEPTH in the current thread."
   "Insert a notmuch style headerline based on HEADERS for a
 message at DEPTH in the current thread."
-  (let ((start (point))
-       (from (notmuch-sanitize
+  (let* ((start (point))
+        (headers (plist-get msg-plist :headers))
+        (duplicate (or (plist-get msg-plist :duplicate) 0))
+        (file-count (length (plist-get msg-plist :filename)))
+        (date (or (and notmuch-show-relative-dates
+                       (plist-get msg-plist :date_relative))
+                  (plist-get headers :Date)))
+        (from (notmuch-sanitize
               (notmuch-show-clean-address (plist-get headers :From)))))
     (when (string-match "\\cR" from)
       ;; If the From header has a right-to-left character add
               (notmuch-show-clean-address (plist-get headers :From)))))
     (when (string-match "\\cR" from)
       ;; If the From header has a right-to-left character add
@@ -549,7 +560,7 @@ message at DEPTH in the current thread."
            " ("
            date
            ") ("
            " ("
            date
            ") ("
-           (notmuch-tag-format-tags tags tags)
+           (notmuch-tag-format-tags tags (or orig-tags tags))
            ")")
     (insert
      (if (> file-count 1)
            ")")
     (insert
      (if (> file-count 1)
@@ -633,8 +644,24 @@ message at DEPTH in the current thread."
                (when show
                  (button-put button :notmuch-lazy-part nil)
                  (notmuch-show-lazy-part lazy-part button))
                (when show
                  (button-put button :notmuch-lazy-part nil)
                  (notmuch-show-lazy-part lazy-part button))
-             ;; else there must be an overlay.
-             (overlay-put overlay 'invisible (not show))
+             (let* ((part (plist-get properties :notmuch-part))
+                    (undisplayer (plist-get part :undisplayer))
+                    (mime-type (plist-get part :computed-type))
+                    (redisplay-data (button-get button
+                                                :notmuch-redisplay-data))
+                    (imagep (string-match "^image/" mime-type)))
+               (cond
+                ((and imagep (not show) undisplayer)
+                 ;; call undisplayer thunk created by gnus.
+                 (funcall undisplayer)
+                 ;; there is an extra newline left
+                 (delete-region
+                  (+ 1 (button-end button))
+                  (+ 2 (button-end button))))
+                ((and imagep show redisplay-data)
+                 (notmuch-show-lazy-part redisplay-data button))
+                (t
+                 (overlay-put overlay 'invisible (not show)))))
              t)))))))
 
 ;;; Part content ID handling
              t)))))))
 
 ;;; Part content ID handling
@@ -837,7 +864,7 @@ will return nil if the CID is unknown or cannot be retrieved."
                    (unless (icalendar-import-buffer file t)
                      (error "Icalendar import error. %s"
                             "See *icalendar-errors* for more information"))
                    (unless (icalendar-import-buffer file t)
                      (error "Icalendar import error. %s"
                             "See *icalendar-errors* for more information"))
-                   (set-buffer (get-file-buffer file))
+                   (set-buffer (find-buffer-visiting file))
                    (setq result (buffer-substring (point-min) (point-max)))
                    (set-buffer-modified-p nil)
                    (kill-buffer (current-buffer)))
                    (setq result (buffer-substring (point-min) (point-max)))
                    (set-buffer-modified-p nil)
                    (kill-buffer (current-buffer)))
@@ -1008,10 +1035,13 @@ will return nil if the CID is unknown or cannot be retrieved."
           (part-end (copy-marker (point) t))
           ;; We have to save the depth as we can't find the depth
           ;; when narrowed.
           (part-end (copy-marker (point) t))
           ;; We have to save the depth as we can't find the depth
           ;; when narrowed.
-          (depth (notmuch-show-get-depth)))
+          (depth (notmuch-show-get-depth))
+          (mime-type (plist-get (cadr part-args) :computed-type)))
       (save-restriction
        (narrow-to-region part-beg part-end)
        (delete-region part-beg part-end)
       (save-restriction
        (narrow-to-region part-beg part-end)
        (delete-region part-beg part-end)
+       (when (and mime-type (string-match "^image/" mime-type))
+         (button-put button :notmuch-redisplay-data part-args))
        (apply #'notmuch-show-insert-bodypart-internal part-args)
        (indent-rigidly part-beg
                        part-end
        (apply #'notmuch-show-insert-bodypart-internal part-args)
        (indent-rigidly part-beg
                        part-end
@@ -1095,14 +1125,18 @@ is t, hide the part initially and show the button."
                             (and deep button)
                             (and high button)
                             (and long button))))
                             (and deep button)
                             (and high button)
                             (and long button))))
-        (content-beg (point)))
+        (content-beg (point))
+        (part-data (list msg part mime-type nth depth button)))
     ;; Store the computed mime-type for later use (e.g. by attachment handlers).
     (plist-put part :computed-type mime-type)
     ;; Store the computed mime-type for later use (e.g. by attachment handlers).
     (plist-put part :computed-type mime-type)
-    (if show-part
-       (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
+    (cond
+     (show-part
+      (apply #'notmuch-show-insert-bodypart-internal part-data)
+      (when (and button (string-match "^image/" mime-type))
+       (button-put button :notmuch-redisplay-data part-data)))
+     (t
       (when button
       (when button
-       (button-put button :notmuch-lazy-part
-                   (list msg part mime-type nth depth button))))
+       (button-put button :notmuch-lazy-part part-data))))
     ;; Some of the body part handlers leave point somewhere up in the
     ;; part, so we make sure that we're down at the end.
     (goto-char (point-max))
     ;; Some of the body part handlers leave point somewhere up in the
     ;; part, so we make sure that we're down at the end.
     (goto-char (point-max))
@@ -1171,8 +1205,6 @@ is out of range."
 (defun notmuch-show-insert-msg (msg depth)
   "Insert the message MSG at depth DEPTH in the current thread."
   (let* ((headers (plist-get msg :headers))
 (defun notmuch-show-insert-msg (msg depth)
   "Insert the message MSG at depth DEPTH in the current thread."
   (let* ((headers (plist-get msg :headers))
-        (duplicate (or (plist-get msg :duplicate) 0))
-        (files (length (plist-get msg :filename)))
         ;; Indentation causes the buffer offset of the start/end
         ;; points to move, so we must use markers.
         message-start message-end
         ;; Indentation causes the buffer offset of the start/end
         ;; points to move, so we must use markers.
         message-start message-end
@@ -1180,11 +1212,7 @@ is out of range."
         headers-start headers-end
         (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
     (setq message-start (point-marker))
         headers-start headers-end
         (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
     (setq message-start (point-marker))
-    (notmuch-show-insert-headerline headers
-                                   (or (and notmuch-show-relative-dates
-                                            (plist-get msg :date_relative))
-                                       (plist-get headers :Date))
-                                   (plist-get msg :tags) depth duplicate files)
+    (notmuch-show-insert-headerline msg depth (plist-get msg :tags))
     (setq content-start (point-marker))
     ;; Set `headers-start' to point after the 'Subject:' header to be
     ;; compatible with the existing implementation. This just sets it
     (setq content-start (point-marker))
     ;; Set `headers-start' to point after the 'Subject:' header to be
     ;; compatible with the existing implementation. This just sets it
@@ -1668,6 +1696,8 @@ All currently available key bindings:
   (setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
   (setq buffer-read-only t)
   (setq truncate-lines t)
   (setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
   (setq buffer-read-only t)
   (setq truncate-lines t)
+  (when (boundp 'untrusted-content)
+    (setq untrusted-content t))
   (setq imenu-prev-index-position-function
        #'notmuch-show-imenu-prev-index-position-function)
   (setq imenu-extract-index-name-function
   (setq imenu-prev-index-position-function
        #'notmuch-show-imenu-prev-index-position-function)
   (setq imenu-extract-index-name-function
@@ -2479,10 +2509,12 @@ kill-ring."
 (defun notmuch-show-stash-mlarchive-link (&optional mla)
   "Copy an ML Archive URI for the current message to the kill-ring.
 
 (defun notmuch-show-stash-mlarchive-link (&optional mla)
   "Copy an ML Archive URI for the current message to the kill-ring.
 
-This presumes that the message is available at the selected Mailing List Archive.
+This presumes that the message is available at the selected
+Mailing List Archive.
 
 
-If optional argument MLA is non-nil, use the provided key instead of prompting
-the user (see `notmuch-show-stash-mlarchive-link-alist')."
+If optional argument MLA is non-nil, use the provided key instead
+of prompting the user (see
+`notmuch-show-stash-mlarchive-link-alist')."
   (interactive)
   (let ((url (cdr (assoc
                   (or mla
   (interactive)
   (let ((url (cdr (assoc
                   (or mla
@@ -2499,12 +2531,15 @@ the user (see `notmuch-show-stash-mlarchive-link-alist')."
        (concat url (notmuch-show-get-message-id t))))))
 
 (defun notmuch-show-stash-mlarchive-link-and-go (&optional mla)
        (concat url (notmuch-show-get-message-id t))))))
 
 (defun notmuch-show-stash-mlarchive-link-and-go (&optional mla)
-  "Copy an ML Archive URI for the current message to the kill-ring and visit it.
+  "Copy an ML Archive URI for the current message to the
+ kill-ring and visit it.
 
 
-This presumes that the message is available at the selected Mailing List Archive.
+This presumes that the message is available at the selected
+Mailing List Archive.
 
 
-If optional argument MLA is non-nil, use the provided key instead of prompting
-the user (see `notmuch-show-stash-mlarchive-link-alist')."
+If optional argument MLA is non-nil, use the provided key instead
+of prompting the user (see
+`notmuch-show-stash-mlarchive-link-alist')."
   (interactive)
   (notmuch-show-stash-mlarchive-link mla)
   (browse-url (current-kill 0 t)))
   (interactive)
   (notmuch-show-stash-mlarchive-link mla)
   (browse-url (current-kill 0 t)))
@@ -2653,7 +2688,9 @@ This function is used as a value for
 `imenu-prev-index-position-function'."
   (if (bobp)
       nil
 `imenu-prev-index-position-function'."
   (if (bobp)
       nil
-    (notmuch-show-previous-message)
+    (if (eobp)
+       (notmuch-show-move-to-message-top)
+      (notmuch-show-goto-message-previous))
     t))
 
 (defun notmuch-show-imenu-extract-index-name-function ()
     t))
 
 (defun notmuch-show-imenu-extract-index-name-function ()
index 959778819617f9cd23498818b2344103fb008c12..811018288f373b1df099fa7b791b27a47e313709 100644 (file)
@@ -159,7 +159,7 @@ place of the tag.
 For example, to replace a tag with another string, simply use
 that string as a formatting expression.  To change the foreground
 of a tag to red, use the expression
 For example, to replace a tag with another string, simply use
 that string as a formatting expression.  To change the foreground
 of a tag to red, use the expression
-  (propertize tag 'face '(:foreground \"red\"))
+  (propertize tag \\='face \\='(:foreground \"red\"))
 
 See also `notmuch-tag-format-image', which can help replace tags
 with images."
 
 See also `notmuch-tag-format-image', which can help replace tags
 with images."
@@ -191,7 +191,7 @@ By default this shows deleted tags with strike-through in red,
 unless strike-through is not available (e.g., emacs is running in
 a terminal) in which case it uses inverse video. To hide deleted
 tags completely set this to
 unless strike-through is not available (e.g., emacs is running in
 a terminal) in which case it uses inverse video. To hide deleted
 tags completely set this to
-  '((\".*\" nil))
+  \\='((\".*\" nil))
 
 See `notmuch-tag-formats' for full documentation."
   :group 'notmuch-show
 
 See `notmuch-tag-formats' for full documentation."
   :group 'notmuch-show
@@ -366,9 +366,9 @@ changed (the normal case) are shown using formats from
 (defcustom notmuch-before-tag-hook nil
   "Hooks that are run before tags of a message are modified.
 
 (defcustom notmuch-before-tag-hook nil
   "Hooks that are run before tags of a message are modified.
 
-'tag-changes' will contain the tags that are about to be added or removed as
+`tag-changes' will contain the tags that are about to be added or removed as
 a list of strings of the form \"+TAG\" or \"-TAG\".
 a list of strings of the form \"+TAG\" or \"-TAG\".
-'query' will be a string containing the search query that determines
+`query' will be a string containing the search query that determines
 the messages that are about to be tagged."
   :type 'hook
   :options '(notmuch-hl-line-mode)
 the messages that are about to be tagged."
   :type 'hook
   :options '(notmuch-hl-line-mode)
@@ -377,9 +377,9 @@ the messages that are about to be tagged."
 (defcustom notmuch-after-tag-hook nil
   "Hooks that are run after tags of a message are modified.
 
 (defcustom notmuch-after-tag-hook nil
   "Hooks that are run after tags of a message are modified.
 
-'tag-changes' will contain the tags that were added or removed as
+`tag-changes' will contain the tags that were added or removed as
 a list of strings of the form \"+TAG\" or \"-TAG\".
 a list of strings of the form \"+TAG\" or \"-TAG\".
-'query' will be a string containing the search query that determines
+`query' will be a string containing the search query that determines
 the messages that were tagged."
   :type 'hook
   :options '(notmuch-hl-line-mode)
 the messages that were tagged."
   :type 'hook
   :options '(notmuch-hl-line-mode)
index 7ceddee22fc49615952469d759595fcb4059e372..aa0d92a75c8fa89698b5ea60b626cc271c27a9a7 100644 (file)
@@ -45,9 +45,6 @@
 (declare-function notmuch-search-previous-thread "notmuch" ())
 (declare-function notmuch-tree-from-search-thread "notmuch" ())
 
 (declare-function notmuch-search-previous-thread "notmuch" ())
 (declare-function notmuch-tree-from-search-thread "notmuch" ())
 
-;; the following variable is defined in notmuch.el
-(defvar notmuch-search-query-string)
-
 ;; this variable distinguishes the unthreaded display from the normal tree display
 (defvar-local notmuch-tree-unthreaded nil
   "A buffer local copy of argument unthreaded to the function notmuch-tree.")
 ;; this variable distinguishes the unthreaded display from the normal tree display
 (defvar-local notmuch-tree-unthreaded nil
   "A buffer local copy of argument unthreaded to the function notmuch-tree.")
@@ -203,7 +200,8 @@ Note that the author string should not contain whitespace
 
 (defface notmuch-tree-match-tree-face
   nil
 
 (defface notmuch-tree-match-tree-face
   nil
-  "Face used in tree mode for the thread tree block graphics in messages matching the query."
+  "Face used in tree mode for the thread tree block graphics in
+messages matching the query."
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
@@ -242,7 +240,8 @@ Note that the author string should not contain whitespace
 
 (defface notmuch-tree-no-match-tree-face
   nil
 
 (defface notmuch-tree-no-match-tree-face
   nil
-  "Face used in tree mode for the thread tree block graphics in messages matching the query."
+  "Face used in tree mode for the thread tree block graphics in
+messages matching the query."
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
@@ -375,6 +374,7 @@ then NAME behaves like CMD."
     (define-key map [remap notmuch-jump-search]   'notmuch-tree-jump-search)
 
     (define-key map "o" 'notmuch-tree-toggle-order)
     (define-key map [remap notmuch-jump-search]   'notmuch-tree-jump-search)
 
     (define-key map "o" 'notmuch-tree-toggle-order)
+    (define-key map "i" 'notmuch-tree-toggle-hide-excluded)
     (define-key map "S" 'notmuch-search-from-tree-current-query)
     (define-key map "U" 'notmuch-unthreaded-from-tree-current-query)
     (define-key map "Z" 'notmuch-tree-from-unthreaded-current-query)
     (define-key map "S" 'notmuch-search-from-tree-current-query)
     (define-key map "U" 'notmuch-unthreaded-from-tree-current-query)
     (define-key map "Z" 'notmuch-tree-from-unthreaded-current-query)
@@ -399,6 +399,7 @@ then NAME behaves like CMD."
     (define-key map "V" 'notmuch-tree-view-raw-message)
     (define-key map "l" 'notmuch-tree-filter)
     (define-key map "t" 'notmuch-tree-filter-by-tag)
     (define-key map "V" 'notmuch-tree-view-raw-message)
     (define-key map "l" 'notmuch-tree-filter)
     (define-key map "t" 'notmuch-tree-filter-by-tag)
+    (define-key map "E" 'notmuch-tree-edit-search)
 
     ;; The main tree view bindings
     (define-key map (kbd "RET") 'notmuch-tree-show-message)
 
     ;; The main tree view bindings
     (define-key map (kbd "RET") 'notmuch-tree-show-message)
@@ -590,7 +591,9 @@ NOT change the database."
   "Call notmuch search with the current query."
   (interactive)
   (notmuch-tree-close-message-window)
   "Call notmuch search with the current query."
   (interactive)
   (notmuch-tree-close-message-window)
-  (notmuch-search (notmuch-tree-get-query)))
+  (notmuch-search (notmuch-tree-get-query)
+                 notmuch-search-oldest-first
+                 notmuch-search-hide-excluded))
 
 (defun notmuch-tree-message-window-kill-hook ()
   "Close the message pane when exiting the show buffer."
 
 (defun notmuch-tree-message-window-kill-hook ()
   "Close the message pane when exiting the show buffer."
@@ -803,7 +806,8 @@ nil otherwise."
                         target
                         nil
                         unthreaded
                         target
                         nil
                         unthreaded
-                        notmuch-search-oldest-first)))
+                        notmuch-search-oldest-first
+                        notmuch-search-hide-excluded)))
 
 (defun notmuch-tree-thread-top ()
   (when (notmuch-tree-get-message-properties)
 
 (defun notmuch-tree-thread-top ()
   (when (notmuch-tree-get-message-properties)
@@ -942,7 +946,9 @@ unchanged ADDRESS if parsing fails."
                    'face face)))
 
      ((string-equal field "subject")
                    'face face)))
 
      ((string-equal field "subject")
-      (let ((bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))
+      (let ((bare-subject
+            (notmuch-sanitize
+             (notmuch-show-strip-re (plist-get headers :Subject))))
            (previous-subject notmuch-tree-previous-subject)
            (face (if match
                      'notmuch-tree-match-subject-face
            (previous-subject notmuch-tree-previous-subject)
            (face (if match
                      'notmuch-tree-match-subject-face
@@ -1016,7 +1022,10 @@ unchanged ADDRESS if parsing fails."
 A message tree is another name for a single sub-thread: i.e., a
 message together with all its descendents."
   (let ((msg (car tree))
 A message tree is another name for a single sub-thread: i.e., a
 message together with all its descendents."
   (let ((msg (car tree))
-       (replies (cadr tree)))
+       (replies (cadr tree))
+       ;; outline level, computed from the message's depth and
+       ;; whether or not it's the first message in the tree.
+       (level (1+ (if (and (eq 0 depth) (not first)) 1 depth))))
     (cond
      ((and (< 0 depth) (not last))
       (push (alist-get 'vertical-tee  notmuch-tree-thread-symbols) tree-status))
     (cond
      ((and (< 0 depth) (not last))
       (push (alist-get 'vertical-tee  notmuch-tree-thread-symbols) tree-status))
@@ -1036,6 +1045,7 @@ message together with all its descendents."
     (setq msg (plist-put msg :first (and first (eq 0 depth))))
     (setq msg (plist-put msg :tree-status tree-status))
     (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
     (setq msg (plist-put msg :first (and first (eq 0 depth))))
     (setq msg (plist-put msg :tree-status tree-status))
     (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
+    (setq msg (plist-put msg :level level))
     (notmuch-tree-goto-and-insert-msg msg)
     (pop tree-status)
     (pop tree-status)
     (notmuch-tree-goto-and-insert-msg msg)
     (pop tree-status)
     (pop tree-status)
@@ -1045,7 +1055,8 @@ message together with all its descendents."
     (notmuch-tree-insert-thread replies (1+ depth) tree-status)))
 
 (defun notmuch-tree-insert-thread (thread depth tree-status)
     (notmuch-tree-insert-thread replies (1+ depth) tree-status)))
 
 (defun notmuch-tree-insert-thread (thread depth tree-status)
-  "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest."
+  "Insert the collection of sibling sub-threads THREAD at depth
+DEPTH in the current forest."
   (let ((n (length thread)))
     (cl-loop for tree in thread
             for count from 1 to n
   (let ((n (length thread)))
     (cl-loop for tree in thread
             for count from 1 to n
@@ -1080,9 +1091,13 @@ Complete list of currently available key bindings:
 
 \\{notmuch-tree-mode-map}"
   (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
 
 \\{notmuch-tree-mode-map}"
   (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
-  (hl-line-mode 1)
+  (when notmuch-hl-line
+    (hl-line-mode 1))
   (setq buffer-read-only t)
   (setq buffer-read-only t)
-  (setq truncate-lines t))
+  (setq truncate-lines t)
+  (when (boundp 'untrusted-content)
+    (setq untrusted-content t))
+  (when notmuch-tree-outline-enabled (notmuch-tree-outline-mode 1)))
 
 (defvar notmuch-tree-process-exit-functions nil
   "Functions called when the process inserting a tree of results finishes.
 
 (defvar notmuch-tree-process-exit-functions nil
   "Functions called when the process inserting a tree of results finishes.
@@ -1109,6 +1124,8 @@ object, and with the tree results buffer as the current buffer.")
                (unless (= exit-status 0)
                  (insert (format " (process returned %d)" exit-status)))
                (insert "\n"))))
                (unless (= exit-status 0)
                  (insert (format " (process returned %d)" exit-status)))
                (insert "\n"))))
+         (when (and notmuch-hl-line (= exit-status 0))
+           (notmuch-hl-line-mode))
          (run-hook-with-args 'notmuch-tree-process-exit-functions proc))))))
 
 (defun notmuch-tree-process-filter (proc string)
          (run-hook-with-args 'notmuch-tree-process-exit-functions proc))))))
 
 (defun notmuch-tree-process-filter (proc string)
@@ -1124,10 +1141,14 @@ object, and with the tree results buffer as the current buffer.")
          (goto-char (point-max))
          (insert string))
        (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread
          (goto-char (point-max))
          (insert string))
        (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread
-                                        results-buf)))))
+                                        results-buf))
+      (with-current-buffer results-buf
+       (when notmuch-hl-line
+         (notmuch-hl-line-mode))))))
 
 (defun notmuch-tree-worker (basic-query &optional query-context target
 
 (defun notmuch-tree-worker (basic-query &optional query-context target
-                                       open-target unthreaded oldest-first)
+                                       open-target unthreaded oldest-first
+                                       exclude)
   "Insert the tree view of the search in the current buffer.
 
 This is is a helper function for notmuch-tree. The arguments are
   "Insert the tree view of the search in the current buffer.
 
 This is is a helper function for notmuch-tree. The arguments are
@@ -1136,6 +1157,7 @@ the same as for the function notmuch-tree."
   (notmuch-tree-mode)
   (add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
   (setq notmuch-search-oldest-first oldest-first)
   (notmuch-tree-mode)
   (add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
   (setq notmuch-search-oldest-first oldest-first)
+  (setq notmuch-search-hide-excluded exclude)
   (setq notmuch-tree-unthreaded unthreaded)
   (setq notmuch-tree-basic-query basic-query)
   (setq notmuch-tree-query-context (if (or (string= query-context "")
   (setq notmuch-tree-unthreaded unthreaded)
   (setq notmuch-tree-basic-query basic-query)
   (setq notmuch-tree-query-context (if (or (string= query-context "")
@@ -1155,14 +1177,15 @@ the same as for the function notmuch-tree."
                              (and query-context
                                   (concat " and (" query-context ")"))))
         (sort-arg (if oldest-first "--sort=oldest-first" "--sort=newest-first"))
                              (and query-context
                                   (concat " and (" query-context ")"))))
         (sort-arg (if oldest-first "--sort=oldest-first" "--sort=newest-first"))
-        (message-arg (if unthreaded "--unthreaded" "--entire-thread")))
+        (message-arg (if unthreaded "--unthreaded" "--entire-thread"))
+        (exclude-arg (if exclude "--exclude=true" "--exclude=false")))
     (when (equal (car (notmuch--process-lines notmuch-command "count" search-args)) "0")
       (setq search-args basic-query))
     (notmuch-tag-clear-cache)
     (let ((proc (notmuch-start-notmuch
                 "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel
                 "show" "--body=false" "--format=sexp" "--format-version=5"
     (when (equal (car (notmuch--process-lines notmuch-command "count" search-args)) "0")
       (setq search-args basic-query))
     (notmuch-tag-clear-cache)
     (let ((proc (notmuch-start-notmuch
                 "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel
                 "show" "--body=false" "--format=sexp" "--format-version=5"
-                sort-arg message-arg search-args))
+                sort-arg message-arg exclude-arg search-args))
          ;; Use a scratch buffer to accumulate partial output.
          ;; This buffer will be killed by the sentinel, which
          ;; should be called no matter how the process dies.
          ;; Use a scratch buffer to accumulate partial output.
          ;; This buffer will be killed by the sentinel, which
          ;; should be called no matter how the process dies.
@@ -1189,8 +1212,19 @@ default sort order is defined by `notmuch-search-oldest-first'."
   (setq notmuch-search-oldest-first (not notmuch-search-oldest-first))
   (notmuch-tree-refresh-view))
 
   (setq notmuch-search-oldest-first (not notmuch-search-oldest-first))
   (notmuch-tree-refresh-view))
 
+(defun notmuch-tree-toggle-hide-excluded ()
+  "Toggle whether to hide excluded messages.
+
+This command toggles whether to hide excluded messages for the current
+search. The default value for this is defined by `notmuch-search-hide-excluded'."
+  (interactive)
+  (setq notmuch-search-hide-excluded (not notmuch-search-hide-excluded))
+  (notmuch-tree-refresh-view))
+
+;;;###autoload
 (defun notmuch-tree (&optional query query-context target buffer-name
 (defun notmuch-tree (&optional query query-context target buffer-name
-                              open-target unthreaded parent-buffer oldest-first)
+                              open-target unthreaded parent-buffer
+                              oldest-first hide-excluded)
   "Display threads matching QUERY in tree view.
 
 The arguments are:
   "Display threads matching QUERY in tree view.
 
 The arguments are:
@@ -1205,7 +1239,15 @@ The arguments are:
       it is nil \"*notmuch-tree\" followed by QUERY is used.
   OPEN-TARGET: If TRUE open the target message in the message pane.
   UNTHREADED: If TRUE only show matching messages in an unthreaded view."
       it is nil \"*notmuch-tree\" followed by QUERY is used.
   OPEN-TARGET: If TRUE open the target message in the message pane.
   UNTHREADED: If TRUE only show matching messages in an unthreaded view."
-  (interactive)
+  (interactive
+   (list
+    ;; Prompt for a query
+    nil
+    ;; Fill other args with nil.
+    nil nil nil nil nil nil
+    ;; Populate these from the default value of these options.
+    (default-value 'notmuch-search-oldest-first)
+    (default-value 'notmuch-search-hide-excluded)))
   (unless query
     (setq query (notmuch-read-query (concat "Notmuch "
                                            (if unthreaded "unthreaded " "tree ")
   (unless query
     (setq query (notmuch-read-query (concat "Notmuch "
                                            (if unthreaded "unthreaded " "tree ")
@@ -1219,17 +1261,27 @@ The arguments are:
     (pop-to-buffer-same-window buffer))
   ;; Don't track undo information for this buffer
   (setq buffer-undo-list t)
     (pop-to-buffer-same-window buffer))
   ;; Don't track undo information for this buffer
   (setq buffer-undo-list t)
-  (notmuch-tree-worker query query-context target open-target unthreaded oldest-first)
+  (notmuch-tree-worker query query-context target open-target
+                      unthreaded oldest-first hide-excluded)
   (setq notmuch-tree-parent-buffer parent-buffer)
   (setq truncate-lines t))
 
 (defun notmuch-unthreaded (&optional query query-context target buffer-name
   (setq notmuch-tree-parent-buffer parent-buffer)
   (setq truncate-lines t))
 
 (defun notmuch-unthreaded (&optional query query-context target buffer-name
-                                    open-target)
+                                    open-target oldest-first hide-excluded)
   "Display threads matching QUERY in unthreaded view.
 
 See function NOTMUCH-TREE for documentation of the arguments"
   "Display threads matching QUERY in unthreaded view.
 
 See function NOTMUCH-TREE for documentation of the arguments"
-  (interactive)
-  (notmuch-tree query query-context target buffer-name open-target t))
+  (interactive
+   (list
+    ;; Prompt for a query
+    nil
+    ;; Fill other args with nil.
+    nil nil nil nil
+    ;; Populate these from the default value of these options.
+    (default-value 'notmuch-search-oldest-first)
+    (default-value 'notmuch-search-hide-excluded)))
+  (notmuch-tree query query-context target buffer-name open-target
+               t nil oldest-first hide-excluded))
 
 (defun notmuch-tree-filter (query)
   "Filter or LIMIT the current search results based on an additional query string.
 
 (defun notmuch-tree-filter (query)
   "Filter or LIMIT the current search results based on an additional query string.
@@ -1257,6 +1309,22 @@ search results and that are also tagged with the given TAG."
   (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto)))
     (notmuch-tree-close-message-window)
     (notmuch-tree (concat notmuch-tree-basic-query " and tag:" tag)
   (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto)))
     (notmuch-tree-close-message-window)
     (notmuch-tree (concat notmuch-tree-basic-query " and tag:" tag)
+                 notmuch-tree-query-context
+                 nil
+                 nil
+                 nil
+                 notmuch-tree-unthreaded
+                 nil
+                 notmuch-search-oldest-first
+                 notmuch-search-hide-excluded)))
+
+(defun notmuch-tree-edit-search (query)
+  "Edit the current search"
+  (interactive (list (read-from-minibuffer "Edit search: "
+                                          notmuch-tree-basic-query)))
+  (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto)))
+    (notmuch-tree-close-message-window)
+    (notmuch-tree query
                  notmuch-tree-query-context
                  nil
                  nil
                  notmuch-tree-query-context
                  nil
                  nil
@@ -1265,6 +1333,180 @@ search results and that are also tagged with the given TAG."
                  nil
                  notmuch-search-oldest-first)))
 
                  nil
                  notmuch-search-oldest-first)))
 
+;;; Tree outline mode
+;;;; Custom variables
+(defcustom notmuch-tree-outline-enabled nil
+  "Whether to automatically activate `notmuch-tree-outline-mode' in tree views."
+  :type 'boolean)
+
+(defcustom notmuch-tree-outline-visibility 'hide-others
+  "Default state of the forest outline for `notmuch-tree-outline-mode'.
+
+This variable controls the state of a forest initially and after
+a movement command.  If set to nil, all trees are displayed while
+the symbol hide-all indicates that all trees in the forest should
+be folded and hide-other that only the first one should be
+unfolded."
+  :type '(choice (const :tag "Show all" nil)
+                (const :tag "Hide others" hide-others)
+                (const :tag "Hide all" hide-all)))
+
+(defcustom notmuch-tree-outline-auto-close nil
+  "Close message and tree windows when moving past the last message."
+  :type 'boolean)
+
+(defcustom notmuch-tree-outline-open-on-next nil
+  "Open new messages under point if they are closed when moving to next one.
+
+When this flag is set, using the command
+`notmuch-tree-outline-next' with point on a header for a new
+message that is not shown will open its `notmuch-show' buffer
+instead of moving point to next matching message."
+  :type 'boolean)
+
+;;;; Helper functions
+(defsubst notmuch-tree-outline--pop-at-end (pop-at-end)
+  (if notmuch-tree-outline-auto-close (not pop-at-end) pop-at-end))
+
+(defun notmuch-tree-outline--set-visibility ()
+  (when (and notmuch-tree-outline-mode (> (point-max) (point-min)))
+    (cl-case notmuch-tree-outline-visibility
+      (hide-others (notmuch-tree-outline-hide-others))
+      (hide-all (outline-hide-body)))))
+
+(defun notmuch-tree-outline--on-exit (proc)
+  (when (eq (process-status proc) 'exit)
+    (notmuch-tree-outline--set-visibility)))
+
+(add-hook 'notmuch-tree-process-exit-functions #'notmuch-tree-outline--on-exit)
+
+(defsubst notmuch-tree-outline--level (&optional props)
+  (or (plist-get (or props (notmuch-tree-get-message-properties)) :level) 0))
+
+(defsubst notmuch-tree-outline--message-open-p ()
+  (and (buffer-live-p notmuch-tree-message-buffer)
+       (get-buffer-window notmuch-tree-message-buffer)
+       (let ((id (notmuch-tree-get-message-id)))
+        (and id
+             (with-current-buffer notmuch-tree-message-buffer
+               (string= (notmuch-show-get-message-id) id))))))
+
+(defsubst notmuch-tree-outline--at-original-match-p ()
+  (and (notmuch-tree-get-prop :match)
+       (equal (notmuch-tree-get-prop :orig-tags)
+              (notmuch-tree-get-prop :tags))))
+
+(defun notmuch-tree-outline--next (prev thread pop-at-end &optional open-new)
+  (cond (thread
+        (notmuch-tree-thread-top)
+        (if prev
+            (outline-backward-same-level 1)
+          (outline-forward-same-level 1))
+        (when (> (notmuch-tree-outline--level) 0) (outline-show-branches))
+        (notmuch-tree-outline--next nil nil pop-at-end t))
+       ((and (or open-new notmuch-tree-outline-open-on-next)
+             (notmuch-tree-outline--at-original-match-p)
+             (not (notmuch-tree-outline--message-open-p)))
+        (notmuch-tree-outline-hide-others t))
+       (t (outline-next-visible-heading (if prev -1 1))
+          (unless (notmuch-tree-get-prop :match)
+            (notmuch-tree-matching-message prev pop-at-end))
+          (notmuch-tree-outline-hide-others t))))
+
+;;;; User commands
+(defun notmuch-tree-outline-hide-others (&optional and-show)
+  "Fold all threads except the one around point.
+If AND-SHOW is t, make the current message visible if it's not."
+  (interactive)
+  (save-excursion
+    (while (and (not (bobp)) (> (notmuch-tree-outline--level) 1))
+      (outline-previous-heading))
+    (outline-hide-sublevels 1))
+  (when (> (notmuch-tree-outline--level) 0)
+    (outline-show-subtree)
+    (when and-show (notmuch-tree-show-message nil))))
+
+(defun notmuch-tree-outline-next (&optional pop-at-end)
+  "Next matching message in a forest, taking care of thread visibility.
+A prefix argument reverses the meaning of `notmuch-tree-outline-auto-close'."
+  (interactive "P")
+  (let ((pop (notmuch-tree-outline--pop-at-end pop-at-end)))
+    (if (null notmuch-tree-outline-visibility)
+       (notmuch-tree-matching-message nil pop)
+      (notmuch-tree-outline--next nil nil pop))))
+
+(defun notmuch-tree-outline-previous (&optional pop-at-end)
+  "Previous matching message in forest, taking care of thread visibility.
+With prefix, quit the tree view if there is no previous message."
+  (interactive "P")
+  (if (null notmuch-tree-outline-visibility)
+      (notmuch-tree-prev-matching-message pop-at-end)
+    (notmuch-tree-outline--next t nil pop-at-end)))
+
+(defun notmuch-tree-outline-next-thread ()
+  "Next matching thread in forest, taking care of thread visibility."
+  (interactive)
+  (if (null notmuch-tree-outline-visibility)
+      (notmuch-tree-next-thread)
+    (notmuch-tree-outline--next nil t nil)))
+
+(defun notmuch-tree-outline-previous-thread ()
+  "Previous matching thread in forest, taking care of thread visibility."
+  (interactive)
+  (if (null notmuch-tree-outline-visibility)
+      (notmuch-tree-prev-thread)
+    (notmuch-tree-outline--next t t nil)))
+
+;;;; Mode definition
+(defvar notmuch-tree-outline-mode-lighter nil
+  "The lighter mark for notmuch-tree-outline mode.
+Usually empty since outline-minor-mode's lighter will be active.")
+
+(define-minor-mode notmuch-tree-outline-mode
+  "Minor mode allowing message trees to be folded as outlines.
+
+When this mode is set, each thread and subthread in the results
+list is treated as a foldable section, with its first message as
+its header.
+
+The mode just makes available in the tree buffer all the
+keybindings in `outline-minor-mode', and binds the following
+additional keys:
+
+\\{notmuch-tree-outline-mode-map}
+
+The customizable variable `notmuch-tree-outline-visibility'
+controls how navigation in the buffer is affected by this mode:
+
+  - If it is set to nil, `notmuch-tree-outline-previous',
+    `notmuch-tree-outline-next', and their thread counterparts
+    behave just as the corresponding notmuch-tree navigation keys
+    when this mode is not enabled.
+
+  - If, on the other hand, `notmuch-tree-outline-visibility' is
+    set to a non-nil value, these commands hiding the outlines of
+    the trees you are not reading as you move to new messages.
+
+To enable notmuch-tree-outline-mode by default in all
+notmuch-tree buffers, just set
+`notmuch-tree-outline-mode-enabled' to t."
+  :lighter notmuch-tree-outline-mode-lighter
+  :keymap `((,(kbd "TAB") . outline-cycle)
+           (,(kbd "M-TAB") . outline-cycle-buffer)
+           ("n" . notmuch-tree-outline-next)
+           ("p" . notmuch-tree-outline-previous)
+           (,(kbd "M-n") . notmuch-tree-outline-next-thread)
+           (,(kbd "M-p") . notmuch-tree-outline-previous-thread))
+  (outline-minor-mode notmuch-tree-outline-mode)
+  (unless (derived-mode-p 'notmuch-tree-mode)
+    (user-error "notmuch-tree-outline-mode is only meaningful for notmuch trees!"))
+  (if notmuch-tree-outline-mode
+      (progn (setq-local outline-regexp "^[^\n]+")
+            (setq-local outline-level #'notmuch-tree-outline--level)
+            (notmuch-tree-outline--set-visibility))
+    (setq-local outline-regexp (default-value 'outline-regexp))
+    (setq-local        outline-level (default-value 'outline-level))))
+
 ;;; _
 
 (provide 'notmuch-tree)
 ;;; _
 
 (provide 'notmuch-tree)
index 653ecc2ae7d5d4e07ff8a87c9ed95315fb3c30bb..fd8a9d1e2296db96e6cf6642bbe0ba35819942ae 100644 (file)
@@ -381,7 +381,7 @@ original filename the sender had."
          (notmuch-wash-subject-to-filename subject 52)))
 
 (defun notmuch-wash-convert-inline-patch-to-part (msg depth)
          (notmuch-wash-subject-to-filename subject 52)))
 
 (defun notmuch-wash-convert-inline-patch-to-part (msg depth)
-  "Convert an inline patch into a fake 'text/x-diff' attachment.
+  "Convert an inline patch into a fake `text/x-diff' attachment.
 
 Given that this function guesses whether a buffer includes a
 patch and then guesses the extent of the patch, there is scope
 
 Given that this function guesses whether a buffer includes a
 patch and then guesses the extent of the patch, there is scope
index 5cb7acd298ae64899d8f7fcf4513627fdcf556cb..69aff3475508e6c518b2f3fbec10e93eaf7d883a 100644 (file)
@@ -126,13 +126,17 @@ there will be called at other points of notmuch execution."
   :type 'file
   :group 'notmuch)
 
   :type 'file
   :group 'notmuch)
 
-(defcustom notmuch-search-hook '(notmuch-hl-line-mode)
+(defcustom notmuch-search-hook nil
   "List of functions to call when notmuch displays the search results."
   :type 'hook
   "List of functions to call when notmuch displays the search results."
   :type 'hook
-  :options '(notmuch-hl-line-mode)
   :group 'notmuch-search
   :group 'notmuch-hooks)
 
   :group 'notmuch-search
   :group 'notmuch-hooks)
 
+(defcustom notmuch-hl-line t
+  "Use hl-line-mode to highlight current thread / message"
+  :type 'boolean
+  :group 'notmuch)
+
 ;;; Mime Utilities
 
 (defun notmuch-foreach-mime-part (function mm-handle)
 ;;; Mime Utilities
 
 (defun notmuch-foreach-mime-part (function mm-handle)
@@ -187,9 +191,11 @@ there will be called at other points of notmuch execution."
     (define-key map "r" 'notmuch-search-reply-to-thread-sender)
     (define-key map "R" 'notmuch-search-reply-to-thread)
     (define-key map "o" 'notmuch-search-toggle-order)
     (define-key map "r" 'notmuch-search-reply-to-thread-sender)
     (define-key map "R" 'notmuch-search-reply-to-thread)
     (define-key map "o" 'notmuch-search-toggle-order)
+    (define-key map "i" 'notmuch-search-toggle-hide-excluded)
     (define-key map "c" 'notmuch-search-stash-map)
     (define-key map "t" 'notmuch-search-filter-by-tag)
     (define-key map "l" 'notmuch-search-filter)
     (define-key map "c" 'notmuch-search-stash-map)
     (define-key map "t" 'notmuch-search-filter-by-tag)
     (define-key map "l" 'notmuch-search-filter)
+    (define-key map "E" 'notmuch-search-edit-search)
     (define-key map [mouse-1] 'notmuch-search-show-thread)
     (define-key map "k" 'notmuch-tag-jump)
     (define-key map "*" 'notmuch-search-tag-all)
     (define-key map [mouse-1] 'notmuch-search-show-thread)
     (define-key map "k" 'notmuch-tag-jump)
     (define-key map "*" 'notmuch-search-tag-all)
@@ -410,6 +416,8 @@ Complete list of currently available key bindings:
   (add-to-invisibility-spec (cons 'ellipsis t))
   (setq truncate-lines t)
   (setq buffer-read-only t)
   (add-to-invisibility-spec (cons 'ellipsis t))
   (setq truncate-lines t)
   (setq buffer-read-only t)
+  (when (boundp 'untrusted-content)
+    (setq untrusted-content t))
   (setq imenu-prev-index-position-function
        #'notmuch-search-imenu-prev-index-position-function)
   (setq imenu-extract-index-name-function
   (setq imenu-prev-index-position-function
        #'notmuch-search-imenu-prev-index-position-function)
   (setq imenu-extract-index-name-function
@@ -549,12 +557,18 @@ Return non-nil on success."
 (defun notmuch-tree-from-search-current-query ()
   "Tree view of current query."
   (interactive)
 (defun notmuch-tree-from-search-current-query ()
   "Tree view of current query."
   (interactive)
-  (notmuch-tree notmuch-search-query-string))
+  (notmuch-tree notmuch-search-query-string
+               nil nil nil nil nil nil
+               notmuch-search-oldest-first
+               notmuch-search-hide-excluded))
 
 (defun notmuch-unthreaded-from-search-current-query ()
   "Unthreaded view of current query."
   (interactive)
 
 (defun notmuch-unthreaded-from-search-current-query ()
   "Unthreaded view of current query."
   (interactive)
-  (notmuch-unthreaded notmuch-search-query-string))
+  (notmuch-unthreaded notmuch-search-query-string
+                     nil nil nil nil
+                     notmuch-search-oldest-first
+                     notmuch-search-hide-excluded))
 
 (defun notmuch-tree-from-search-thread ()
   "Show the selected thread with notmuch-tree."
 
 (defun notmuch-tree-from-search-thread ()
   "Show the selected thread with notmuch-tree."
@@ -563,7 +577,9 @@ Return non-nil on success."
                notmuch-search-query-string
                nil
                (notmuch-prettify-subject (notmuch-search-find-subject))
                notmuch-search-query-string
                nil
                (notmuch-prettify-subject (notmuch-search-find-subject))
-               t nil (current-buffer)))
+               t nil (current-buffer)
+               notmuch-search-oldest-first
+               notmuch-search-hide-excluded))
 
 (defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
   "Begin composing a reply-all to the entire current thread in a new buffer."
 
 (defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
   "Begin composing a reply-all to the entire current thread in a new buffer."
@@ -840,6 +856,7 @@ non-authors is found, assume that all of the authors match."
              overlay)
          (insert invisible-string)
          (setq overlay (make-overlay start (point)))
              overlay)
          (insert invisible-string)
          (setq overlay (make-overlay start (point)))
+         (overlay-put overlay 'evaporate t)
          (overlay-put overlay 'invisible 'ellipsis)
          (overlay-put overlay 'isearch-open-invisible #'delete-overlay)))
       (insert padding))))
          (overlay-put overlay 'invisible 'ellipsis)
          (overlay-put overlay 'isearch-open-invisible #'delete-overlay)))
       (insert padding))))
@@ -914,6 +931,16 @@ sets the :orig-tag property."
        (notmuch-sexp-parse-partial-list 'notmuch-search-append-result
                                         results-buf))
       (with-current-buffer results-buf
        (notmuch-sexp-parse-partial-list 'notmuch-search-append-result
                                         results-buf))
       (with-current-buffer results-buf
+       (when (and notmuch-hl-line
+                  ;; If we know where the cursor will end up (from
+                  ;; the call to notmuch-search), avoid redrawing the
+                  ;; hl-line overlay until the buffer is sufficiently
+                  ;; filled. This check is intended as an
+                  ;; optimization to reduce flicker.
+                  (>=
+                   (line-number-at-pos (point-max) t)
+                   (or notmuch-search-target-line -1)))
+         (notmuch-hl-line-mode))
        (notmuch--search-hook-wrapper)))))
 
 ;;; Commands (and some helper functions used by them)
        (notmuch--search-hook-wrapper)))))
 
 ;;; Commands (and some helper functions used by them)
@@ -1033,14 +1060,16 @@ PROMPT is the string to prompt with."
 
 (put 'notmuch-search 'notmuch-doc "Search for messages.")
 ;;;###autoload
 
 (put 'notmuch-search 'notmuch-doc "Search for messages.")
 ;;;###autoload
-(defun notmuch-search (&optional query oldest-first target-thread target-line
-                                no-display)
+(defun notmuch-search (&optional query oldest-first hide-excluded target-thread
+                                target-line no-display)
   "Display threads matching QUERY in a notmuch-search buffer.
 
 If QUERY is nil, it is read interactively from the minibuffer.
 Other optional parameters are used as follows:
 
   OLDEST-FIRST: A Boolean controlling the sort order of returned threads
   "Display threads matching QUERY in a notmuch-search buffer.
 
 If QUERY is nil, it is read interactively from the minibuffer.
 Other optional parameters are used as follows:
 
   OLDEST-FIRST: A Boolean controlling the sort order of returned threads
+  HIDE-EXCLUDED: A boolean controlling whether to omit threads with excluded
+                 tags.
   TARGET-THREAD: A thread ID (without the thread: prefix) that will be made
                  current if it appears in the search results.
   TARGET-LINE: The line number to move to if the target thread does not
   TARGET-THREAD: A thread ID (without the thread: prefix) that will be made
                  current if it appears in the search results.
   TARGET-LINE: The line number to move to if the target thread does not
@@ -1055,22 +1084,24 @@ the configured default sort order."
    (list
     ;; Prompt for a query
     nil
    (list
     ;; Prompt for a query
     nil
-    ;; Use the default search order (if we're doing a search from a
-    ;; search buffer, ignore any buffer-local overrides)
-    (default-value 'notmuch-search-oldest-first)))
+    ;; Use the default search order and exclude value (if we're doing a
+    ;; search from a search buffer, ignore any buffer-local overrides)
+    (default-value 'notmuch-search-oldest-first)
+    (default-value 'notmuch-search-hide-excluded)))
 
   (let* ((query (or query (notmuch-read-query "Notmuch search: ")))
         (buffer (get-buffer-create (notmuch-search-buffer-title query))))
 
   (let* ((query (or query (notmuch-read-query "Notmuch search: ")))
         (buffer (get-buffer-create (notmuch-search-buffer-title query))))
-    (if no-display
-       (set-buffer buffer)
-      (pop-to-buffer-same-window buffer))
+    (set-buffer buffer)
     (notmuch-search-mode)
     (notmuch-search-mode)
+    (unless no-display
+      (pop-to-buffer-same-window buffer))
     ;; Don't track undo information for this buffer
     (setq buffer-undo-list t)
     (setq notmuch-search-query-string query)
     (setq notmuch-search-oldest-first oldest-first)
     (setq notmuch-search-target-thread target-thread)
     (setq notmuch-search-target-line target-line)
     ;; Don't track undo information for this buffer
     (setq buffer-undo-list t)
     (setq notmuch-search-query-string query)
     (setq notmuch-search-oldest-first oldest-first)
     (setq notmuch-search-target-thread target-thread)
     (setq notmuch-search-target-line target-line)
+    (setq notmuch-search-hide-excluded hide-excluded)
     (notmuch-tag-clear-cache)
     (when (get-buffer-process buffer)
       (error "notmuch search process already running for query `%s'" query))
     (notmuch-tag-clear-cache)
     (when (get-buffer-process buffer)
       (error "notmuch search process already running for query `%s'" query))
@@ -1084,6 +1115,9 @@ the configured default sort order."
                     (if oldest-first
                         "--sort=oldest-first"
                       "--sort=newest-first")
                     (if oldest-first
                         "--sort=oldest-first"
                       "--sort=newest-first")
+                    (if hide-excluded
+                        "--exclude=true"
+                      "--exclude=false")
                     query)))
          ;; Use a scratch buffer to accumulate partial output.
          ;; This buffer will be killed by the sentinel, which
                     query)))
          ;; Use a scratch buffer to accumulate partial output.
          ;; This buffer will be killed by the sentinel, which
@@ -1104,11 +1138,21 @@ same relative position within the new buffer."
   (interactive)
   (notmuch-search notmuch-search-query-string
                  notmuch-search-oldest-first
   (interactive)
   (notmuch-search notmuch-search-query-string
                  notmuch-search-oldest-first
+                 notmuch-search-hide-excluded
                  (notmuch-search-find-thread-id 'bare)
                  (line-number-at-pos)
                  t)
   (goto-char (point-min)))
 
                  (notmuch-search-find-thread-id 'bare)
                  (line-number-at-pos)
                  t)
   (goto-char (point-min)))
 
+(defun notmuch-search-toggle-hide-excluded ()
+  "Toggle whether to hide excluded messages.
+
+This command toggles whether to hide excluded messages for the current
+search. The default value for this is defined by `notmuch-search-hide-excluded'."
+  (interactive)
+  (setq notmuch-search-hide-excluded (not notmuch-search-hide-excluded))
+  (notmuch-search-refresh-view))
+
 (defun notmuch-search-toggle-order ()
   "Toggle the current search order.
 
 (defun notmuch-search-toggle-order ()
   "Toggle the current search order.
 
@@ -1137,7 +1181,8 @@ current search results AND the additional query string provided."
     (notmuch-search (if (string= grouped-original-query "*")
                        grouped-query
                      (concat grouped-original-query " and " grouped-query))
     (notmuch-search (if (string= grouped-original-query "*")
                        grouped-query
                      (concat grouped-original-query " and " grouped-query))
-                   notmuch-search-oldest-first)))
+                   notmuch-search-oldest-first
+                   notmuch-search-hide-excluded)))
 
 (defun notmuch-search-filter-by-tag (tag)
   "Filter the current search results based on a single TAG.
 
 (defun notmuch-search-filter-by-tag (tag)
   "Filter the current search results based on a single TAG.
@@ -1148,13 +1193,22 @@ search results and that are also tagged with the given TAG."
    (list (notmuch-select-tag-with-completion "Filter by tag: "
                                             notmuch-search-query-string)))
   (notmuch-search (concat notmuch-search-query-string " and tag:" tag)
    (list (notmuch-select-tag-with-completion "Filter by tag: "
                                             notmuch-search-query-string)))
   (notmuch-search (concat notmuch-search-query-string " and tag:" tag)
-                 notmuch-search-oldest-first))
+                 notmuch-search-oldest-first
+                 notmuch-search-hide-excluded))
 
 (defun notmuch-search-by-tag (tag)
   "Display threads matching TAG in a notmuch-search buffer."
   (interactive
    (list (notmuch-select-tag-with-completion "Notmuch search tag: ")))
 
 (defun notmuch-search-by-tag (tag)
   "Display threads matching TAG in a notmuch-search buffer."
   (interactive
    (list (notmuch-select-tag-with-completion "Notmuch search tag: ")))
-  (notmuch-search (concat "tag:" tag)))
+  (notmuch-search (concat "tag:" tag)
+                 (default-value 'notmuch-search-oldest-first)
+                 (default-value 'notmuch-search-hide-excluded)))
+
+(defun notmuch-search-edit-search (query)
+  "Edit the current search"
+  (interactive (list (read-from-minibuffer "Edit search: "
+                                          notmuch-search-query-string)))
+  (notmuch-search query notmuch-search-oldest-first))
 
 ;;;###autoload
 (defun notmuch ()
 
 ;;;###autoload
 (defun notmuch ()
index 503a0c8b585323dd59fad974b55ca47b7399fad6..acb397ecc14efbd093f167df41747b69c2277cbb 100644 (file)
@@ -416,7 +416,8 @@ _expand_path (void *ctx, const char *key, const char *val)
 
 notmuch_status_t
 _notmuch_config_load_from_file (notmuch_database_t *notmuch,
 
 notmuch_status_t
 _notmuch_config_load_from_file (notmuch_database_t *notmuch,
-                               GKeyFile *file)
+                               GKeyFile *file,
+                               char **status_string)
 {
     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
     gchar **groups = NULL, **keys, *val;
 {
     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
     gchar **groups = NULL, **keys, *val;
@@ -435,6 +436,7 @@ _notmuch_config_load_from_file (notmuch_database_t *notmuch,
        for (gchar **keys_p = keys; *keys_p; keys_p++) {
            char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp,  *keys_p);
            char *normalized_val;
        for (gchar **keys_p = keys; *keys_p; keys_p++) {
            char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp,  *keys_p);
            char *normalized_val;
+           GError *gerr = NULL;
 
            /* If we opened from a given path, do not overwrite it */
            if (strcmp (absolute_key, "database.path") == 0 &&
 
            /* If we opened from a given path, do not overwrite it */
            if (strcmp (absolute_key, "database.path") == 0 &&
@@ -442,7 +444,14 @@ _notmuch_config_load_from_file (notmuch_database_t *notmuch,
                notmuch->xapian_db)
                continue;
 
                notmuch->xapian_db)
                continue;
 
-           val = g_key_file_get_string (file, *grp, *keys_p, NULL);
+           val = g_key_file_get_string (file, *grp, *keys_p, &gerr);
+           if (gerr) {
+               if (status_string)
+                   IGNORE_RESULT (asprintf (status_string,
+                                            "GLib: %s\n",
+                                            gerr->message));
+               g_error_free (gerr);
+           }
            if (! val) {
                status = NOTMUCH_STATUS_FILE_ERROR;
                goto DONE;
            if (! val) {
                status = NOTMUCH_STATUS_FILE_ERROR;
                goto DONE;
@@ -599,6 +608,12 @@ _notmuch_config_key_to_string (notmuch_config_key_t key)
        return "database.autocommit";
     case NOTMUCH_CONFIG_EXTRA_HEADERS:
        return "show.extra_headers";
        return "database.autocommit";
     case NOTMUCH_CONFIG_EXTRA_HEADERS:
        return "show.extra_headers";
+    case NOTMUCH_CONFIG_AUTHORS_SEPARATOR:
+       return "search.authors_separator";
+    case NOTMUCH_CONFIG_AUTHORS_MATCHED_SEPARATOR:
+       return "search.authors_matched_separator";
+    case NOTMUCH_CONFIG_INDEX_AS_TEXT:
+       return "index.as_text";
     default:
        return NULL;
     }
     default:
        return NULL;
     }
@@ -642,10 +657,15 @@ _notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
        else
            email = _get_email_from_passwd_file (notmuch);
        return email;
        else
            email = _get_email_from_passwd_file (notmuch);
        return email;
+    case NOTMUCH_CONFIG_INDEX_AS_TEXT:
     case NOTMUCH_CONFIG_NEW_IGNORE:
        return "";
     case NOTMUCH_CONFIG_AUTOCOMMIT:
        return "8000";
     case NOTMUCH_CONFIG_NEW_IGNORE:
        return "";
     case NOTMUCH_CONFIG_AUTOCOMMIT:
        return "8000";
+    case NOTMUCH_CONFIG_AUTHORS_SEPARATOR:
+       return ", ";
+    case NOTMUCH_CONFIG_AUTHORS_MATCHED_SEPARATOR:
+       return "| ";
     case NOTMUCH_CONFIG_EXTRA_HEADERS:
     case NOTMUCH_CONFIG_HOOK_DIR:
     case NOTMUCH_CONFIG_BACKUP_DIR:
     case NOTMUCH_CONFIG_EXTRA_HEADERS:
     case NOTMUCH_CONFIG_HOOK_DIR:
     case NOTMUCH_CONFIG_BACKUP_DIR:
index b9be4e2255ad2d03ab82c55b5cad06c7721f2368..61232f1ae3ffcc51bb0dd6b96c91e1b9296e33d3 100644 (file)
@@ -291,6 +291,10 @@ struct _notmuch_database {
 
     /* Track what parameters were specified when opening */
     notmuch_open_param_t params;
 
     /* Track what parameters were specified when opening */
     notmuch_open_param_t params;
+
+    /* list of regular expressions to check for text indexing */
+    regex_t *index_as_text;
+    size_t index_as_text_length;
 };
 
 /* Prior to database version 3, features were implied by the database
 };
 
 /* Prior to database version 3, features were implied by the database
index c05d70d34842f1fd1f264cc61a45fbb6348b95ca..737a3f3060677b4e7fd5a55fbe5268082f0867f2 100644 (file)
@@ -1454,9 +1454,11 @@ notmuch_database_remove_message (notmuch_database_t *notmuch,
                                                        &message);
 
     if (status == NOTMUCH_STATUS_SUCCESS && message) {
                                                        &message);
 
     if (status == NOTMUCH_STATUS_SUCCESS && message) {
-       status = _notmuch_message_remove_filename (message, filename);
+       if (notmuch_message_count_files (message) > 1) {
+           status = _notmuch_message_remove_filename (message, filename);
+       }
        if (status == NOTMUCH_STATUS_SUCCESS)
        if (status == NOTMUCH_STATUS_SUCCESS)
-           _notmuch_message_delete (message);
+           status = _notmuch_message_delete (message);
        else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
            _notmuch_message_sync (message);
 
        else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
            _notmuch_message_sync (message);
 
@@ -1573,3 +1575,15 @@ notmuch_database_status_string (const notmuch_database_t *notmuch)
 {
     return notmuch->status_string;
 }
 {
     return notmuch->status_string;
 }
+
+bool
+_notmuch_database_indexable_as_text (notmuch_database_t *notmuch, const char *mime_string)
+{
+    for (size_t i = 0; i < notmuch->index_as_text_length; i++) {
+       if (regexec (&notmuch->index_as_text[i], mime_string, 0, NULL, 0) == 0) {
+           return true;
+       }
+    }
+
+    return false;
+}
index 728bfb22ab69c4eef52795cf86d7971c7d42e2fe..629dcb222be8545cfde471ce5184f96c88115881 100644 (file)
@@ -380,6 +380,23 @@ _index_pkcs7_part (notmuch_message_t *message,
                   GMimeObject *part,
                   _notmuch_message_crypto_t *msg_crypto);
 
                   GMimeObject *part,
                   _notmuch_message_crypto_t *msg_crypto);
 
+static bool
+_indexable_as_text (notmuch_message_t *message, GMimeObject *part)
+{
+    GMimeContentType *content_type = g_mime_object_get_content_type (part);
+    notmuch_database_t *notmuch = notmuch_message_get_database (message);
+
+    if (content_type) {
+       char *mime_string = g_mime_content_type_get_mime_type (content_type);
+       if (mime_string) {
+           bool ret = _notmuch_database_indexable_as_text (notmuch, mime_string);
+           g_free (mime_string);
+           return ret;
+       }
+    }
+    return false;
+}
+
 /* Callback to generate terms for each mime part of a message. */
 static void
 _index_mime_part (notmuch_message_t *message,
 /* Callback to generate terms for each mime part of a message. */
 static void
 _index_mime_part (notmuch_message_t *message,
@@ -497,9 +514,11 @@ _index_mime_part (notmuch_message_t *message,
        _notmuch_message_add_term (message, "tag", "attachment");
        _notmuch_message_gen_terms (message, "attachment", filename);
 
        _notmuch_message_add_term (message, "tag", "attachment");
        _notmuch_message_gen_terms (message, "attachment", filename);
 
-       /* XXX: Would be nice to call out to something here to parse
-        * the attachment into text and then index that. */
-       goto DONE;
+       if (! _indexable_as_text (message, part)) {
+           /* XXX: Would be nice to call out to something here to parse
+            * the attachment into text and then index that. */
+           goto DONE;
+       }
     }
 
     byte_array = g_byte_array_new ();
     }
 
     byte_array = g_byte_array_new ();
index d5afa30c8e2a3e4d74d50a02efa15659febafbaf..7f5203403723d3514e8d85f738d43b546adcabe7 100644 (file)
 #include "database-private.h"
 #include "message-private.h"
 
 #include "database-private.h"
 #include "message-private.h"
 
+#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
+
+static void
+_log_xapian_exception (const char *where, notmuch_message_t *message,  const Xapian::Error error)
+{
+    notmuch_database_t *notmuch = notmuch_message_get_database (message);
+
+    _notmuch_database_log (notmuch,
+                          "A Xapian exception occurred at %s: %s\n",
+                          where,
+                          error.get_msg ().c_str ());
+    notmuch->exception_reported = true;
+}
+
 notmuch_status_t
 notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value)
 {
 notmuch_status_t
 notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value)
 {
@@ -83,10 +97,15 @@ _notmuch_message_modify_property (notmuch_message_t *message, const char *key, c
 
     term = talloc_asprintf (message, "%s=%s", key, value);
 
 
     term = talloc_asprintf (message, "%s=%s", key, value);
 
-    if (delete_it)
-       private_status = _notmuch_message_remove_term (message, "property", term);
-    else
-       private_status = _notmuch_message_add_term (message, "property", term);
+    try {
+       if (delete_it)
+           private_status = _notmuch_message_remove_term (message, "property", term);
+       else
+           private_status = _notmuch_message_add_term (message, "property", term);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
 
     if (private_status)
        return COERCE_STATUS (private_status,
 
     if (private_status)
        return COERCE_STATUS (private_status,
@@ -123,15 +142,22 @@ _notmuch_message_remove_all_properties (notmuch_message_t *message, const char *
     if (status)
        return status;
 
     if (status)
        return status;
 
-    _notmuch_message_invalidate_metadata (message, "property");
     if (key)
        term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
                                       prefix ? "" : "=");
     else
        term_prefix = _find_prefix ("property");
 
     if (key)
        term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
                                       prefix ? "" : "=");
     else
        term_prefix = _find_prefix ("property");
 
-    /* XXX better error reporting ? */
-    _notmuch_message_remove_terms (message, term_prefix);
+    try {
+       /* XXX better error reporting ? */
+       _notmuch_message_remove_terms (message, term_prefix);
+    } catch (Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (message, error);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    if (! _notmuch_message_frozen (message))
+       _notmuch_message_sync (message);
 
     return NOTMUCH_STATUS_SUCCESS;
 }
 
     return NOTMUCH_STATUS_SUCCESS;
 }
index 1c87f8c0c2eb16ecb6b0f372f22e01072ff4fcae..46638f800997d4c5250b3592e4be8ee79abc2a21 100644 (file)
@@ -719,6 +719,8 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
            /* Ignore failure to remove non-existent term. */
        }
     }
            /* Ignore failure to remove non-existent term. */
        }
     }
+
+    _notmuch_message_invalidate_metadata (message, "property");
 }
 
 
 }
 
 
@@ -941,6 +943,7 @@ _notmuch_message_add_filename (notmuch_message_t *message,
 {
     const char *relative, *directory;
     notmuch_status_t status;
 {
     const char *relative, *directory;
     notmuch_status_t status;
+    notmuch_private_status_t private_status;
     void *local = talloc_new (message);
     char *direntry;
 
     void *local = talloc_new (message);
     char *direntry;
 
@@ -964,10 +967,17 @@ _notmuch_message_add_filename (notmuch_message_t *message,
 
     /* New file-direntry allows navigating to this message with
      * notmuch_directory_get_child_files() . */
 
     /* New file-direntry allows navigating to this message with
      * notmuch_directory_get_child_files() . */
-    status = COERCE_STATUS (_notmuch_message_add_term (message, "file-direntry", direntry),
-                           "adding file-direntry term");
-    if (status)
-       return status;
+    private_status = _notmuch_message_add_term (message, "file-direntry", direntry);
+    switch (private_status) {
+    case NOTMUCH_PRIVATE_STATUS_SUCCESS:
+       break;
+    case NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG:
+       _notmuch_database_log (message->notmuch, "filename too long for file-direntry term: %s\n",
+                              filename);
+       return NOTMUCH_STATUS_PATH_ERROR;
+    default:
+       return COERCE_STATUS (private_status, "adding file-direntry term");
+    }
 
     status = _notmuch_message_add_folder_terms (message, directory);
     if (status)
 
     status = _notmuch_message_add_folder_terms (message, directory);
     if (status)
@@ -1383,21 +1393,22 @@ _notmuch_message_delete (notmuch_message_t *message)
     if (status)
        return status;
 
     if (status)
        return status;
 
-    message->notmuch->writable_xapian_db->delete_document (message->doc_id);
-
-    /* if this was a ghost to begin with, we are done */
-    private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
-    if (private_status)
-       return COERCE_STATUS (private_status,
-                             "Error trying to determine whether message was a ghost");
-    if (is_ghost)
-       return NOTMUCH_STATUS_SUCCESS;
-
-    /* look for a non-ghost message in the same thread */
     try {
        Xapian::PostingIterator thread_doc, thread_doc_end;
        Xapian::PostingIterator mail_doc, mail_doc_end;
 
     try {
        Xapian::PostingIterator thread_doc, thread_doc_end;
        Xapian::PostingIterator mail_doc, mail_doc_end;
 
+       /* look for a non-ghost message in the same thread */
+       /* if this was a ghost to begin with, we are done */
+       private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
+       if (private_status)
+           return COERCE_STATUS (private_status,
+                                 "Error trying to determine whether message was a ghost");
+
+       message->notmuch->writable_xapian_db->delete_document (message->doc_id);
+
+       if (is_ghost)
+           return NOTMUCH_STATUS_SUCCESS;
+
        _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc,
                                        &thread_doc_end);
        _notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_doc_end);
        _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc,
                                        &thread_doc_end);
        _notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_doc_end);
index 1d3d2b0c9db220d36723728c044f6b0b8e71cfef..367e23e610d0220df108eed1cc078694b10bfea9 100644 (file)
@@ -259,6 +259,10 @@ _notmuch_database_filename_to_direntry (void *ctx,
                                        notmuch_find_flags_t flags,
                                        char **direntry);
 
                                        notmuch_find_flags_t flags,
                                        char **direntry);
 
+bool
+_notmuch_database_indexable_as_text (notmuch_database_t *notmuch,
+                                    const char *mime_string);
+
 /* directory.cc */
 
 notmuch_directory_t *
 /* directory.cc */
 
 notmuch_directory_t *
@@ -722,7 +726,7 @@ notmuch_status_t
 _notmuch_config_load_from_database (notmuch_database_t *db);
 
 notmuch_status_t
 _notmuch_config_load_from_database (notmuch_database_t *db);
 
 notmuch_status_t
-_notmuch_config_load_from_file (notmuch_database_t *db, GKeyFile *file);
+_notmuch_config_load_from_file (notmuch_database_t *db, GKeyFile *file, char **status_string);
 
 notmuch_status_t
 _notmuch_config_load_defaults (notmuch_database_t *db);
 
 notmuch_status_t
 _notmuch_config_load_defaults (notmuch_database_t *db);
index 0b0540b13437f6161adbba741352ae9df0a2cdfd..937fa24edf069aa3bb2cef64ecf0c05e0851bf56 100644 (file)
@@ -293,6 +293,8 @@ typedef struct _notmuch_indexopts notmuch_indexopts_t;
  *
  * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
  *
  *
  * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
  *
+ * NOTMUCH_STATUS_PATH_ERROR: filename is too long
+ *
  * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to create the
  *     database file (such as permission denied, or file not found,
  *     etc.), or the database already exists.
  * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to create the
  *     database file (such as permission denied, or file not found,
  *     etc.), or the database already exists.
@@ -720,7 +722,8 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch);
  *
  * The UUID is a NUL-terminated opaque string that uniquely identifies
  * this database.  Two revision numbers are only comparable if they
  *
  * The UUID is a NUL-terminated opaque string that uniquely identifies
  * this database.  Two revision numbers are only comparable if they
- * have the same database UUID.
+ * have the same database UUID. The string 'uuid' is owned by notmuch
+ * and should not be freed or modified by the user.
  */
 unsigned long
 notmuch_database_get_revision (notmuch_database_t *notmuch,
  */
 unsigned long
 notmuch_database_get_revision (notmuch_database_t *notmuch,
@@ -1170,7 +1173,10 @@ notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out
  *
  *     query = notmuch_query_create (database, query_string);
  *
  *
  *     query = notmuch_query_create (database, query_string);
  *
- *     for (messages = notmuch_query_search_messages (query);
+ *     if (notmuch_query_search_messages (query, &messages) != NOTMUCH_STATUS_SUCCESS)
+ *         return EXIT_FAILURE;
+ *
+ *     for (;
  *          notmuch_messages_valid (messages);
  *          notmuch_messages_move_to_next (messages))
  *     {
  *          notmuch_messages_valid (messages);
  *          notmuch_messages_move_to_next (messages))
  *     {
@@ -2558,6 +2564,9 @@ typedef enum {
     NOTMUCH_CONFIG_USER_NAME,
     NOTMUCH_CONFIG_AUTOCOMMIT,
     NOTMUCH_CONFIG_EXTRA_HEADERS,
     NOTMUCH_CONFIG_USER_NAME,
     NOTMUCH_CONFIG_AUTOCOMMIT,
     NOTMUCH_CONFIG_EXTRA_HEADERS,
+    NOTMUCH_CONFIG_INDEX_AS_TEXT,
+    NOTMUCH_CONFIG_AUTHORS_SEPARATOR,
+    NOTMUCH_CONFIG_AUTHORS_MATCHED_SEPARATOR,
     NOTMUCH_CONFIG_LAST
 } notmuch_config_key_t;
 
     NOTMUCH_CONFIG_LAST
 } notmuch_config_key_t;
 
index 67ff868c2e334eef1895f9ebc801420662508ae3..463e38bf0d8f733fe933da033ab993d828255fb7 100644 (file)
@@ -249,6 +249,11 @@ _choose_database_path (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_NO_DATABASE;
     }
 
        return NOTMUCH_STATUS_NO_DATABASE;
     }
 
+    if (*message) {
+       free (*message);
+       *message = NULL;
+    }
+
     return NOTMUCH_STATUS_SUCCESS;
 }
 
     return NOTMUCH_STATUS_SUCCESS;
 }
 
@@ -320,6 +325,8 @@ _alloc_notmuch (const char *database_path, const char *config_path, const char *
     notmuch->transaction_count = 0;
     notmuch->transaction_threshold = 0;
     notmuch->view = 1;
     notmuch->transaction_count = 0;
     notmuch->transaction_threshold = 0;
     notmuch->view = 1;
+    notmuch->index_as_text = NULL;
+    notmuch->index_as_text_length = 0;
 
     notmuch->params = NOTMUCH_PARAM_NONE;
     if (database_path)
 
     notmuch->params = NOTMUCH_PARAM_NONE;
     if (database_path)
@@ -427,6 +434,53 @@ _load_database_state (notmuch_database_t *notmuch)
        notmuch, notmuch->xapian_db->get_uuid ().c_str ());
 }
 
        notmuch, notmuch->xapian_db->get_uuid ().c_str ());
 }
 
+/* XXX This should really be done lazily, but the error reporting path in the indexing code
+ * would need to be redone to report any errors.
+ */
+notmuch_status_t
+_ensure_index_as_text (notmuch_database_t *notmuch, char **message)
+{
+    int nregex = 0;
+    regex_t *regexv = NULL;
+
+    if (notmuch->index_as_text)
+       return NOTMUCH_STATUS_SUCCESS;
+
+    for (notmuch_config_values_t *list = notmuch_config_get_values (notmuch,
+                                                                   NOTMUCH_CONFIG_INDEX_AS_TEXT);
+        notmuch_config_values_valid (list);
+        notmuch_config_values_move_to_next (list)) {
+       regex_t *new_regex;
+       int rerr;
+       const char *str = notmuch_config_values_get (list);
+       size_t len = strlen (str);
+
+       /* str must be non-empty, because n_c_get_values skips empty
+        * strings */
+       assert (len > 0);
+
+       regexv = talloc_realloc (notmuch, regexv, regex_t, nregex + 1);
+       new_regex = &regexv[nregex];
+
+       rerr = regcomp (new_regex, str, REG_EXTENDED | REG_NOSUB);
+       if (rerr) {
+           size_t error_size = regerror (rerr, new_regex, NULL, 0);
+           char *error = (char *) talloc_size (str, error_size);
+
+           regerror (rerr, new_regex, error, error_size);
+           IGNORE_RESULT (asprintf (message, "Error in index.as_text: %s: %s\n", error, str));
+
+           return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+       }
+       nregex++;
+    }
+
+    notmuch->index_as_text = regexv;
+    notmuch->index_as_text_length = nregex;
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 static notmuch_status_t
 _finish_open (notmuch_database_t *notmuch,
              const char *profile,
 static notmuch_status_t
 _finish_open (notmuch_database_t *notmuch,
              const char *profile,
@@ -506,7 +560,7 @@ _finish_open (notmuch_database_t *notmuch,
            goto DONE;
 
        if (key_file)
            goto DONE;
 
        if (key_file)
-           status = _notmuch_config_load_from_file (notmuch, key_file);
+           status = _notmuch_config_load_from_file (notmuch, key_file, &message);
        if (status)
            goto DONE;
 
        if (status)
            goto DONE;
 
@@ -531,6 +585,10 @@ _finish_open (notmuch_database_t *notmuch,
        if (status)
            goto DONE;
 
        if (status)
            goto DONE;
 
+       status = _ensure_index_as_text (notmuch, &message);
+       if (status)
+           goto DONE;
+
        autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
        if (unlikely (! autocommit_str)) {
            INTERNAL_ERROR ("missing configuration for autocommit");
        autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
        if (unlikely (! autocommit_str)) {
            INTERNAL_ERROR ("missing configuration for autocommit");
@@ -908,7 +966,7 @@ notmuch_database_load_config (const char *database_path,
     }
 
     if (key_file) {
     }
 
     if (key_file) {
-       status = _notmuch_config_load_from_file (notmuch, key_file);
+       status = _notmuch_config_load_from_file (notmuch, key_file, &message);
        if (status)
            goto DONE;
     }
        if (status)
            goto DONE;
     }
index 9cadbc1359d3fcb2d3813bab7eae5d0a91d6f096..930888e9535ed2271936e9c74653343d182c4a5a 100644 (file)
@@ -3,6 +3,7 @@
 #if HAVE_SFSEXP
 #include "sexp.h"
 #include "unicode-util.h"
 #if HAVE_SFSEXP
 #include "sexp.h"
 #include "unicode-util.h"
+#include "xapian-extra.h"
 
 /* _sexp is used for file scope symbols to avoid clashing with
  * definitions from sexp.h */
 
 /* _sexp is used for file scope symbols to avoid clashing with
  * definitions from sexp.h */
@@ -39,82 +40,99 @@ typedef enum {
 /*
  * define bitwise operators to hide casts */
 
 /*
  * define bitwise operators to hide casts */
 
-inline _sexp_flag_t
+static inline _sexp_flag_t
 operator| (_sexp_flag_t a, _sexp_flag_t b)
 {
     return static_cast<_sexp_flag_t>(
        static_cast<unsigned>(a) | static_cast<unsigned>(b));
 }
 
 operator| (_sexp_flag_t a, _sexp_flag_t b)
 {
     return static_cast<_sexp_flag_t>(
        static_cast<unsigned>(a) | static_cast<unsigned>(b));
 }
 
-inline _sexp_flag_t
+static inline _sexp_flag_t
 operator& (_sexp_flag_t a, _sexp_flag_t b)
 {
     return static_cast<_sexp_flag_t>(
        static_cast<unsigned>(a) & static_cast<unsigned>(b));
 }
 
 operator& (_sexp_flag_t a, _sexp_flag_t b)
 {
     return static_cast<_sexp_flag_t>(
        static_cast<unsigned>(a) & static_cast<unsigned>(b));
 }
 
+typedef enum {
+    SEXP_INITIAL_MATCH_ALL,
+    SEXP_INITIAL_MATCH_NOTHING,
+} _sexp_initial_t;
+
+static inline Xapian::Query
+_sexp_initial_query (_sexp_initial_t initial)
+{
+    switch (initial) {
+    case SEXP_INITIAL_MATCH_ALL:
+       return xapian_query_match_all ();
+    case SEXP_INITIAL_MATCH_NOTHING:
+       return Xapian::Query::MatchNothing;
+    }
+    INTERNAL_ERROR ("invalid initial sexp value %d", initial);
+}
+
 typedef struct  {
     const char *name;
     Xapian::Query::op xapian_op;
 typedef struct  {
     const char *name;
     Xapian::Query::op xapian_op;
-    Xapian::Query initial;
+    _sexp_initial_t initial;
     _sexp_flag_t flags;
 } _sexp_prefix_t;
 
 static _sexp_prefix_t prefixes[] =
 {
     _sexp_flag_t flags;
 } _sexp_prefix_t;
 
 static _sexp_prefix_t prefixes[] =
 {
-    { "and",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "and",            Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_NONE },
       SEXP_FLAG_NONE },
-    { "attachment",     Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "attachment",     Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
-    { "body",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "body",           Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD },
       SEXP_FLAG_FIELD },
-    { "date",           Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
+    { "date",           Xapian::Query::OP_INVALID,      SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_RANGE },
       SEXP_FLAG_RANGE },
-    { "from",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "from",           Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
-    { "folder",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+    { "folder",         Xapian::Query::OP_OR,           SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND |
       SEXP_FLAG_PATHNAME },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND |
       SEXP_FLAG_PATHNAME },
-    { "id",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+    { "id",             Xapian::Query::OP_OR,           SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
-    { "infix",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
+    { "infix",          Xapian::Query::OP_INVALID,      SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
-    { "is",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "is",             Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
-    { "lastmod",           Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
+    { "lastmod",           Xapian::Query::OP_INVALID,      SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_RANGE },
       SEXP_FLAG_RANGE },
-    { "matching",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "matching",       Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_DO_EXPAND },
       SEXP_FLAG_DO_EXPAND },
-    { "mid",            Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+    { "mid",            Xapian::Query::OP_OR,           SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
-    { "mimetype",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "mimetype",       Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
-    { "not",            Xapian::Query::OP_AND_NOT,      Xapian::Query::MatchAll,
+    { "not",            Xapian::Query::OP_AND_NOT,      SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_NONE },
       SEXP_FLAG_NONE },
-    { "of",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "of",             Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_DO_EXPAND },
       SEXP_FLAG_DO_EXPAND },
-    { "or",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+    { "or",             Xapian::Query::OP_OR,           SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_NONE },
       SEXP_FLAG_NONE },
-    { "path",           Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+    { "path",           Xapian::Query::OP_OR,           SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX |
       SEXP_FLAG_PATHNAME },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX |
       SEXP_FLAG_PATHNAME },
-    { "property",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "property",       Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
-    { "query",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchNothing,
+    { "query",          Xapian::Query::OP_INVALID,      SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
-    { "regex",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
+    { "regex",          Xapian::Query::OP_INVALID,      SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
-    { "rx",             Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
+    { "rx",             Xapian::Query::OP_INVALID,      SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
-    { "starts-with",    Xapian::Query::OP_WILDCARD,     Xapian::Query::MatchAll,
+    { "starts-with",    Xapian::Query::OP_WILDCARD,     SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_SINGLE },
       SEXP_FLAG_SINGLE },
-    { "subject",        Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "subject",        Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
-    { "tag",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "tag",            Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
-    { "thread",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+    { "thread",         Xapian::Query::OP_OR,           SEXP_INITIAL_MATCH_NOTHING,
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
-    { "to",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+    { "to",             Xapian::Query::OP_AND,          SEXP_INITIAL_MATCH_ALL,
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
     { }
 };
       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
     { }
 };
@@ -318,7 +336,8 @@ _sexp_expand_query (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
     }
 
        return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
     }
 
-    status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
+    status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op,
+                                 _sexp_initial_query (prefix->initial), sx,
                                  subquery);
     if (status)
        return status;
                                  subquery);
     if (status)
        return status;
@@ -370,7 +389,8 @@ _sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
 
     parent = &user_prefix;
 
 
     parent = &user_prefix;
 
-    return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+    return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND,
+                               xapian_query_match_all (),
                                sx->list->next, output);
 }
 
                                sx->list->next, output);
 }
 
@@ -520,7 +540,7 @@ _sexp_parse_range (notmuch_database_t *notmuch,  const _sexp_prefix_t *prefix,
 
     /* empty range matches everything */
     if (! sx) {
 
     /* empty range matches everything */
     if (! sx) {
-       output = Xapian::Query::MatchAll;
+       output = xapian_query_match_all ();
        return NOTMUCH_STATUS_SUCCESS;
     }
 
        return NOTMUCH_STATUS_SUCCESS;
     }
 
@@ -628,7 +648,7 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
 
     /* Empty list */
     if (! sx->list) {
 
     /* Empty list */
     if (! sx->list) {
-       output = Xapian::Query::MatchAll;
+       output = xapian_query_match_all ();
        return NOTMUCH_STATUS_SUCCESS;
     }
 
        return NOTMUCH_STATUS_SUCCESS;
     }
 
@@ -704,7 +724,8 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
                return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
            }
 
                return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
            }
 
-           return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
+           return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op,
+                                       _sexp_initial_query (prefix->initial),
                                        sx->list->next, output);
        }
     }
                                        sx->list->next, output);
        }
     }
index 707f6222b16bd001c17097439e49804a6babc4ef..1c60c122c8e9eedd22b2e5481e8e189719551994 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "notmuch-private.h"
 #include "database-private.h"
 
 #include "notmuch-private.h"
 #include "database-private.h"
+#include "xapian-extra.h"
 
 #include <glib.h> /* GHashTable, GPtrArray */
 
 
 #include <glib.h> /* GHashTable, GPtrArray */
 
@@ -186,7 +187,7 @@ _notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
 {
     try {
        if (query_string == "" || query_string == "*") {
 {
     try {
        if (query_string == "" || query_string == "*") {
-           output = Xapian::Query::MatchAll;
+           output = xapian_query_match_all ();
        } else {
            output =
                notmuch->query_parser->
        } else {
            output =
                notmuch->query_parser->
index 539915d892f9529d3d011dfa7611bb37aba718d2..3a775261fc3a1c2f31b82f345766e231fbf45f81 100644 (file)
@@ -25,6 +25,7 @@
 #include "regexp-fields.h"
 #include "notmuch-private.h"
 #include "database-private.h"
 #include "regexp-fields.h"
 #include "notmuch-private.h"
 #include "database-private.h"
+#include "xapian-extra.h"
 
 notmuch_status_t
 compile_regex (regex_t &regexp, const char *str, std::string &msg)
 
 notmuch_status_t
 compile_regex (regex_t &regexp, const char *str, std::string &msg)
@@ -200,7 +201,7 @@ RegexpFieldProcessor::operator() (const std::string & str)
     if (str.empty ()) {
        if (options & NOTMUCH_FIELD_PROBABILISTIC) {
            return Xapian::Query (Xapian::Query::OP_AND_NOT,
     if (str.empty ()) {
        if (options & NOTMUCH_FIELD_PROBABILISTIC) {
            return Xapian::Query (Xapian::Query::OP_AND_NOT,
-                                 Xapian::Query::MatchAll,
+                                 xapian_query_match_all (),
                                  Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
        } else {
            return Xapian::Query (term_prefix);
                                  Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
        } else {
            return Xapian::Query (term_prefix);
index e3a81b4fe1457a030154fb3cb05cfc59cd3bb059..99bc2ea2450fe7a188f527106d5f45fb711f8f10 100644 (file)
@@ -86,10 +86,14 @@ _notmuch_string_map_append (notmuch_string_map_t *map,
 static int
 cmppair (const void *pa, const void *pb)
 {
 static int
 cmppair (const void *pa, const void *pb)
 {
+    int cmp = 0;
     notmuch_string_pair_t *a = (notmuch_string_pair_t *) pa;
     notmuch_string_pair_t *b = (notmuch_string_pair_t *) pb;
 
     notmuch_string_pair_t *a = (notmuch_string_pair_t *) pa;
     notmuch_string_pair_t *b = (notmuch_string_pair_t *) pb;
 
-    return strcmp (a->key, b->key);
+    cmp = strcmp (a->key, b->key);
+    if (cmp == 0)
+       cmp = strcmp (a->value, b->value);
+    return cmp;
 }
 
 static void
 }
 
 static void
index 60e9a666ec1b348c7b2f413c6da94d4655188831..168a9e8b397fc9418c2a873c3b56e2cab0494c74 100644 (file)
@@ -122,21 +122,28 @@ _thread_add_matched_author (notmuch_thread_t *thread,
 
 /* Construct an authors string from matched_authors_array and
  * authors_array. The string contains matched authors first, then
 
 /* Construct an authors string from matched_authors_array and
  * authors_array. The string contains matched authors first, then
- * non-matched authors (with the two groups separated by '|'). Within
- * each group, authors are listed in date order. */
+ * non-matched authors (with the two groups separated by '|' or the custom
+ * separator defined in the configuration). Within each group, authors are
+ * listed in date order and separated by ',' or the custom separator defined in
+ * the configuration. */
 static void
 _resolve_thread_authors_string (notmuch_thread_t *thread)
 {
     unsigned int i;
     char *author;
     int first_non_matched_author = 1;
 static void
 _resolve_thread_authors_string (notmuch_thread_t *thread)
 {
     unsigned int i;
     char *author;
     int first_non_matched_author = 1;
+    const char *authors_sep = notmuch_config_get (thread->notmuch,
+                                                 NOTMUCH_CONFIG_AUTHORS_SEPARATOR);
+    const char *authors_matched_sep = notmuch_config_get (thread->notmuch,
+                                                         NOTMUCH_CONFIG_AUTHORS_MATCHED_SEPARATOR);
 
     /* First, list all matched authors in date order. */
     for (i = 0; i < thread->matched_authors_array->len; i++) {
        author = (char *) g_ptr_array_index (thread->matched_authors_array, i);
        if (thread->authors)
 
     /* First, list all matched authors in date order. */
     for (i = 0; i < thread->matched_authors_array->len; i++) {
        author = (char *) g_ptr_array_index (thread->matched_authors_array, i);
        if (thread->authors)
-           thread->authors = talloc_asprintf (thread, "%s%s",
+           thread->authors = talloc_asprintf (thread, "%s%s%s",
                                               thread->authors,
                                               thread->authors,
+                                              authors_sep,
                                               author);
        else
            thread->authors = author;
                                               author);
        else
            thread->authors = author;
@@ -149,12 +156,14 @@ _resolve_thread_authors_string (notmuch_thread_t *thread)
                                          author, NULL, NULL))
            continue;
        if (first_non_matched_author) {
                                          author, NULL, NULL))
            continue;
        if (first_non_matched_author) {
-           thread->authors = talloc_asprintf (thread, "%s%s",
+           thread->authors = talloc_asprintf (thread, "%s%s%s",
                                               thread->authors,
                                               thread->authors,
+                                              authors_matched_sep,
                                               author);
        } else {
                                               author);
        } else {
-           thread->authors = talloc_asprintf (thread, "%s%s",
+           thread->authors = talloc_asprintf (thread, "%s%s%s",
                                               thread->authors,
                                               thread->authors,
+                                              authors_sep,
                                               author);
        }
 
                                               author);
        }
 
index 21b49908ae246c1bffd0b7cc242b79b663cbb800..1a87240d3c21de8e51736ede9b408cdd36e23652 100644 (file)
@@ -77,6 +77,8 @@ typedef struct notmuch_show_params {
     bool output_body;
     int duplicate;
     int part;
     bool output_body;
     int duplicate;
     int part;
+    int offset;
+    int limit;
     _notmuch_crypto_t crypto;
     bool include_html;
     GMimeStream *out_stream;
     _notmuch_crypto_t crypto;
     bool include_html;
     GMimeStream *out_stream;
index e9456d794456c080e81abbd6a00efcb6b123c3c4..f7e59f1ea3de3010e16f7e6039475ee6ca47dc37 100644 (file)
@@ -40,11 +40,13 @@ static const struct config_group {
        "database",
        " Database configuration\n"
        "\n"
        "database",
        " Database configuration\n"
        "\n"
-       " The only value supported here is 'path' which should be the top-level\n"
-       " directory where your mail currently exists and to where mail will be\n"
-       " delivered in the future. Files should be individual email messages.\n"
-       " Notmuch will store its database within a sub-directory of the path\n"
-       " configured here named \".notmuch\".\n"
+       " Supported values are 'mail_root' and 'path'. The recommended option\n"
+       " is to set 'mail_root' to the directory where your mail currently exists\n"
+       " and to where mail will be delivered in the future. Files should be\n"
+       " individual email messages. By default notmuch will store its database\n"
+       " in $XDG_DATA_HOME/notmuch; you can override this by setting 'path'.\n"
+       " If only 'path' is set, this directory is for 'mail_root' and for\n"
+       " the database location (in a subdirectory called \".notmuch\").\n"
     },
     {
        "user",
     },
     {
        "user",
@@ -278,18 +280,24 @@ notmuch_conffile_open (notmuch_database_t *notmuch,
        return NULL;
     }
 
        return NULL;
     }
 
-    if (config->is_new)
-       g_key_file_set_comment (config->key_file, NULL, NULL,
-                               toplevel_config_comment, NULL);
-
     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
        const char *name = group_comment_table[i].group_name;
        if (! g_key_file_has_group (config->key_file,  name)) {
            /* Force group to exist before adding comment */
            g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
            g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
     for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
        const char *name = group_comment_table[i].group_name;
        if (! g_key_file_has_group (config->key_file,  name)) {
            /* Force group to exist before adding comment */
            g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
            g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
-           g_key_file_set_comment (config->key_file, name, NULL,
-                                   group_comment_table[i].comment, NULL);
+           if (config->is_new && (i == 0) ) {
+               const char *comment;
+
+               comment = talloc_asprintf (config, "%s\n%s",
+                                          toplevel_config_comment,
+                                          group_comment_table[i].comment);
+               g_key_file_set_comment (config->key_file, name, NULL, comment,
+                                       NULL);
+           } else {
+               g_key_file_set_comment (config->key_file, name, NULL,
+                                       group_comment_table[i].comment, NULL);
+           }
        }
     }
     return config;
        }
     }
     return config;
index ceb86fbc14dfa97db10dfec87732452a648edc0b..ee87bec611c632cec23875ae7386d8e641081536 100644 (file)
@@ -31,7 +31,6 @@ import locale as _locale
 import logging as _logging
 import os as _os
 import re as _re
 import logging as _logging
 import os as _os
 import re as _re
-import shutil as _shutil
 import subprocess as _subprocess
 import sys as _sys
 import tempfile as _tempfile
 import subprocess as _subprocess
 import sys as _sys
 import tempfile as _tempfile
@@ -248,13 +247,13 @@ def count_messages(prefix=None):
         stdout=_subprocess.PIPE, wait=True)
     if status != 0:
         _LOG.error("failed to run notmuch config")
         stdout=_subprocess.PIPE, wait=True)
     if status != 0:
         _LOG.error("failed to run notmuch config")
-        sys.exit(1)
+        _sys.exit(1)
     return int(stdout.rstrip())
 
 def get_tags(prefix=None):
     "Get a list of tags with a given prefix."
     (status, stdout, stderr) = _spawn(
     return int(stdout.rstrip())
 
 def get_tags(prefix=None):
     "Get a list of tags with a given prefix."
     (status, stdout, stderr) = _spawn(
-        args=['notmuch', 'search', '--query=sexp', '--output=tags', _tag_query(prefix)],
+        args=['notmuch', 'search', '--exclude=false', '--query=sexp', '--output=tags', _tag_query(prefix)],
         stdout=_subprocess.PIPE, wait=True)
     return [tag for tag in stdout.splitlines()]
 
         stdout=_subprocess.PIPE, wait=True)
     return [tag for tag in stdout.splitlines()]
 
@@ -369,7 +368,7 @@ class CachedIndex:
         _git(args=['read-tree', self.current_treeish], wait=True)
 
 
         _git(args=['read-tree', self.current_treeish], wait=True)
 
 
-def check_safe_fraction(status):
+def _check_fraction(change):
     safe = 0.1
     conf = _notmuch_config_get ('git.safe_fraction')
     if conf and conf != '':
     safe = 0.1
     conf = _notmuch_config_get ('git.safe_fraction')
     if conf and conf != '':
@@ -377,10 +376,10 @@ def check_safe_fraction(status):
 
     total = count_messages (TAG_PREFIX)
     if total == 0:
 
     total = count_messages (TAG_PREFIX)
     if total == 0:
-        _LOG.error('No existing tags with given prefix, stopping.'.format(safe))
+        _LOG.error('No existing tags with given prefix, stopping.')
         _LOG.error('Use --force to override.')
         exit(1)
         _LOG.error('Use --force to override.')
         exit(1)
-    change = len(status['added'])+len(status['deleted'])
+
     fraction = change/total
     _LOG.debug('total messages {:d}, change: {:d}, fraction: {:f}'.format(total,change,fraction))
     if fraction > safe:
     fraction = change/total
     _LOG.debug('total messages {:d}, change: {:d}, fraction: {:f}'.format(total,change,fraction))
     if fraction > safe:
@@ -388,6 +387,25 @@ def check_safe_fraction(status):
         _LOG.error('Use --force to override or reconfigure git.safe_fraction.')
         exit(1)
 
         _LOG.error('Use --force to override or reconfigure git.safe_fraction.')
         exit(1)
 
+def check_safe_fraction(status):
+
+    change = len(status['added'])+len(status['deleted'])
+    _check_fraction(change)
+
+def check_diff_fraction():
+
+    # check number of directories (i.e. messages) changed.
+    change_set = set()
+
+    with _git(args=['diff', '--name-only', 'HEAD', '@{upstream}'],
+              stdout=_subprocess.PIPE) as git:
+        for path in git.stdout:
+            change_set.add(_os.path.dirname(path))
+
+    change=len(change_set)
+    _check_fraction(change)
+
+
 def commit(treeish='HEAD', message=None, force=False):
     """
     Commit prefix-matching tags from the notmuch database to Git.
 def commit(treeish='HEAD', message=None, force=False):
     """
     Commit prefix-matching tags from the notmuch database to Git.
@@ -620,6 +638,15 @@ def push(repository=None, refspecs=None):
     _git(args=args, wait=True)
 
 
     _git(args=args, wait=True)
 
 
+def reset(force=False):
+    """
+    reset the local git branch to match the remote one
+    """
+    if not force:
+        check_diff_fraction()
+
+    _git(args=["reset","--soft","origin/master"],wait=True)
+
 def status():
     """
     Show pending updates in notmuch or git repo.
 def status():
     """
     Show pending updates in notmuch or git repo.
@@ -719,7 +746,7 @@ class DatabaseCache:
                 self._known[id] = False
         else:
             (_, stdout, stderr) = _spawn(
                 self._known[id] = False
         else:
             (_, stdout, stderr) = _spawn(
-                args=['notmuch', 'search', '--output=files', 'id:{0}'.format(id)],
+                args=['notmuch', 'search', '--exclude=false', '--output=files', 'id:{0}'.format(id)],
                 stdout=_subprocess.PIPE,
                 wait=True)
             self._known[id] = stdout != None
                 stdout=_subprocess.PIPE,
                 wait=True)
             self._known[id] = stdout != None
@@ -1048,6 +1075,7 @@ if __name__ == '__main__':
             'merge',
             'pull',
             'push',
             'merge',
             'pull',
             'push',
+            'reset',
             'status',
             ]:
         func = locals()[command]
             'status',
             ]:
         func = locals()[command]
@@ -1142,6 +1170,10 @@ if __name__ == '__main__':
                     'Refspec (usually a branch name) to push.  See '
                     'the <refspec> entry in the OPTIONS section of '
                     'git-push(1) for other possibilities.'))
                     'Refspec (usually a branch name) to push.  See '
                     'the <refspec> entry in the OPTIONS section of '
                     'git-push(1) for other possibilities.'))
+        elif command == 'reset':
+            subparser.add_argument(
+                '-f', '--force', action='store_true',
+                help='reset a large fraction of tags.')
 
     args = parser.parse_args()
 
 
     args = parser.parse_args()
 
index 346e64697aaf02763a38eb09e93ef428348aae4a..4a53e3ebcc064cc531b8cb6255c422ff35938b30 100644 (file)
@@ -413,6 +413,10 @@ add_file (notmuch_database_t *notmuch, const char *filename,
     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
        fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
        break;
     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
        fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
        break;
+    case NOTMUCH_STATUS_PATH_ERROR:
+       fprintf (stderr, "Note: Ignoring non-indexable path: %s\n", filename);
+       (void) print_status_database ("add_file", notmuch, status);
+       break;
     case NOTMUCH_STATUS_FILE_ERROR:
        /* Someone renamed/removed the file between scandir and now. */
        state->vanished_files++;
     case NOTMUCH_STATUS_FILE_ERROR:
        /* Someone renamed/removed the file between scandir and now. */
        state->vanished_files++;
index ee9efa7448d78c171ec618d561bb1fa450576864..8c23f8212f4a04fcb652e2d0050f7aad0ba2d1db 100644 (file)
@@ -1159,6 +1159,18 @@ do_show_threaded (void *ctx,
     notmuch_thread_t *thread;
     notmuch_messages_t *messages;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
     notmuch_thread_t *thread;
     notmuch_messages_t *messages;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
+    int i;
+
+    if (params->offset < 0) {
+       unsigned count;
+       notmuch_status_t s = notmuch_query_count_threads (query, &count);
+       if (print_status_query ("notmuch show", query, s))
+           return 1;
+
+       params->offset += count;
+       if (params->offset < 0)
+           params->offset = 0;
+    }
 
     status = notmuch_query_search_threads (query, &threads);
     if (print_status_query ("notmuch show", query, status))
 
     status = notmuch_query_search_threads (query, &threads);
     if (print_status_query ("notmuch show", query, status))
@@ -1166,11 +1178,16 @@ do_show_threaded (void *ctx,
 
     sp->begin_list (sp);
 
 
     sp->begin_list (sp);
 
-    for (;
-        notmuch_threads_valid (threads);
-        notmuch_threads_move_to_next (threads)) {
+    for (i = 0;
+        notmuch_threads_valid (threads) && (params->limit < 0 || i < params->offset + params->limit);
+        notmuch_threads_move_to_next (threads), i++) {
        thread = notmuch_threads_get (threads);
 
        thread = notmuch_threads_get (threads);
 
+       if (i < params->offset) {
+           notmuch_thread_destroy (thread);
+           continue;
+       }
+
        messages = notmuch_thread_get_toplevel_messages (thread);
 
        if (messages == NULL)
        messages = notmuch_thread_get_toplevel_messages (thread);
 
        if (messages == NULL)
@@ -1201,6 +1218,18 @@ do_show_unthreaded (void *ctx,
     notmuch_message_t *message;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
     notmuch_bool_t excluded;
     notmuch_message_t *message;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
     notmuch_bool_t excluded;
+    int i;
+
+    if (params->offset < 0) {
+       unsigned count;
+       notmuch_status_t s = notmuch_query_count_messages (query, &count);
+       if (print_status_query ("notmuch show", query, s))
+           return 1;
+
+       params->offset += count;
+       if (params->offset < 0)
+           params->offset = 0;
+    }
 
     status = notmuch_query_search_messages (query, &messages);
     if (print_status_query ("notmuch show", query, status))
 
     status = notmuch_query_search_messages (query, &messages);
     if (print_status_query ("notmuch show", query, status))
@@ -1208,9 +1237,13 @@ do_show_unthreaded (void *ctx,
 
     sp->begin_list (sp);
 
 
     sp->begin_list (sp);
 
-    for (;
-        notmuch_messages_valid (messages);
-        notmuch_messages_move_to_next (messages)) {
+    for (i = 0;
+        notmuch_messages_valid (messages) && (params->limit < 0 || i < params->offset + params->limit);
+        notmuch_messages_move_to_next (messages), i++) {
+       if (i < params->offset) {
+           continue;
+       }
+
        sp->begin_list (sp);
        sp->begin_list (sp);
 
        sp->begin_list (sp);
        sp->begin_list (sp);
 
@@ -1287,6 +1320,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
     notmuch_show_params_t params = {
        .part = -1,
        .duplicate = 0,
     notmuch_show_params_t params = {
        .part = -1,
        .duplicate = 0,
+       .offset = 0,
+       .limit = -1, /* unlimited */
        .omit_excluded = true,
        .output_body = true,
        .crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
        .omit_excluded = true,
        .output_body = true,
        .crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
@@ -1328,6 +1363,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
        { .opt_bool = &params.output_body, .name = "body" },
        { .opt_bool = &params.include_html, .name = "include-html" },
        { .opt_int = &params.duplicate, .name = "duplicate" },
        { .opt_bool = &params.output_body, .name = "body" },
        { .opt_bool = &params.include_html, .name = "include-html" },
        { .opt_int = &params.duplicate, .name = "duplicate" },
+       { .opt_int = &params.limit, .name = "limit" },
+       { .opt_int = &params.offset, .name = "offset" },
        { .opt_inherit = notmuch_shared_options },
        { }
     };
        { .opt_inherit = notmuch_shared_options },
        { }
     };
@@ -1362,6 +1399,14 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
            fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
            return EXIT_FAILURE;
        }
            fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
            return EXIT_FAILURE;
        }
+       if (params.crypto.decrypt != NOTMUCH_DECRYPT_FALSE
+           && params.crypto.decrypt != NOTMUCH_DECRYPT_AUTO) {
+           fprintf (stderr, "Warning: mbox format does not support decryption (ignored)\n");
+       }
+       if (params.crypto.verify) {
+           fprintf (stderr,
+                    "Warning: mbox format does not support signature verification (ignored)\n");
+       }
     } else if (format == NOTMUCH_FORMAT_RAW) {
        /* raw format only supports single message display */
        single_message = true;
     } else if (format == NOTMUCH_FORMAT_RAW) {
        /* raw format only supports single message display */
        single_message = true;
index 37286b8f491af6428c0d22c3222a01f85039a369..814b9e42a59f46ae675f24f688005ec22ab58914 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -563,6 +563,12 @@ main (int argc, char *argv[])
                                               NULL,
                                               &notmuch,
                                               &status_string);
                                               NULL,
                                               &notmuch,
                                               &status_string);
+       if (status_string) {
+           fputs (status_string, stderr);
+           free (status_string);
+           status_string = NULL;
+       }
+
        switch (status) {
        case NOTMUCH_STATUS_NO_CONFIG:
            if (! (command->mode & NOTMUCH_COMMAND_CONFIG_CREATE)) {
        switch (status) {
        case NOTMUCH_STATUS_NO_CONFIG:
            if (! (command->mode & NOTMUCH_COMMAND_CONFIG_CREATE)) {
@@ -584,6 +590,8 @@ main (int argc, char *argv[])
        case NOTMUCH_STATUS_SUCCESS:
            break;
        default:
        case NOTMUCH_STATUS_SUCCESS:
            break;
        default:
+           fputs ("Error: unable to load config file.\n", stderr);
+           ret = 1;
            goto DONE;
        }
 
            goto DONE;
        }
 
index 59b37b1b8ff033092591ede74f9b1f7eb551ec62..1ca0df2f664c9e711a6ec0977ff76b8fce60036e 100644 (file)
@@ -24,11 +24,11 @@ Getting set up to run tests:
 First, you need to get the corpus.  If you don't already have the gpg
 key for David Bremner, run
 
 First, you need to get the corpus.  If you don't already have the gpg
 key for David Bremner, run
 
-   % gpg --search 'david@tethera.net'
+   % gpg --locate-external-key 'david@tethera.net'
 
 This should get you a key with fingerprint
 
 
 This should get you a key with fingerprint
 
-    815B 6398 2A79 F8E7 C727  86C4 762B 57BB 7842 06AD
+    7A18 807F 100A 4570 C596  8420 7E4E 65C8 720B 706B
 
 (the last 8 digits are printed as the "key id").
 
 
 (the last 8 digits are printed as the "key id").
 
index 9c895d6a910145266dde528039a61caabcc8fa70..47fdb0c27ab54df243403ba5a1cfe3595d256c8b 100755 (executable)
@@ -11,4 +11,13 @@ time_run 'tag * +existing_tag' "notmuch tag +new_tag '*'"
 time_run 'tag * -existing_tag' "notmuch tag -new_tag '*'"
 time_run 'tag * -missing_tag' "notmuch tag -new_tag '*'"
 
 time_run 'tag * -existing_tag' "notmuch tag -new_tag '*'"
 time_run 'tag * -missing_tag' "notmuch tag -new_tag '*'"
 
+time_run 'tag * +maildir_flag F' "notmuch tag +flagged '*'"
+time_run 'tag * -maildir_flag F' "notmuch tag -flagged '*'"
+time_run 'tag * +maildir_flag P' "notmuch tag +passed '*'"
+time_run 'tag * -maildir_flag P' "notmuch tag -passed '*'"
+time_run 'tag * +maildir_flag D' "notmuch tag +draft '*'"
+time_run 'tag * -maildir_flag D' "notmuch tag -draft '*'"
+time_run 'tag * +maildir_flag S' "notmuch tag -unread '*'"
+time_run 'tag * -maildir_flag S' "notmuch tag +unread '*'"
+
 time_done
 time_done
diff --git a/performance-test/download/notmuch-email-corpus-0.5.tar.xz.asc b/performance-test/download/notmuch-email-corpus-0.5.tar.xz.asc
new file mode 100644 (file)
index 0000000..2318c2f
--- /dev/null
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCAAdFiEEkiyHYXwaY0SiY6fqA0U5G1WqFSEFAmR119gACgkQA0U5G1Wq
+FSHlSQ/+NSRj27PEZjaP2I+3j+rsMG3pnVckNcuOQyfgjJ+zEagMZyRu3vaIA/pX
+xtBrNIX4l4CQIkqwyNjsuqJdzh6S3DeCWSEr1Q+GSBki+wQCBiRuDYY2HQoDezEK
+4bMfniEWZpKJD8PfIabz0OOqMUsfXEYMd9kefew5/J4OGnDIv8E5pKfqvDNNO4rW
+MhZ9w9uR9wkvmfmpO66kAgTfLllwiyNHWoWnzQfNmqM8eULFn7XxM1PEZShUEqXf
+pTWCqqm5OyUcy8f+gy9Mb7DRRvnwLpHHRQlCzzH2c+ENQRpt1ErsgVKpHTVk4UsB
+EML+zwyWEaQg7xVKWXRJDuGCF47S1GCQNUtvtx57HJl6Ds6N2mlr2KEGaI7qtiz5
+5hdaTc0L/TVN0WS+uCdfdDDozFErf1kwhA6Jnpi0YNNdK+wpFzj7ISvA+DNHwJ75
+TLBuJIU/h3QfX3NDC5xDbsWAgpv7a84e7ePO6+kAVkHsNYDbFjiunr5fRbqDsJcJ
+B+aVGhKvFZbziC84Dn5Ae9Lpa40fQlxbdb+So2nDIiuR3P33vt7wr/e2ptVfrqkn
+a1DM96n03VWexwEDMye3b3rOTXsN5Ul87zucg9xWm5JT75NGuqJ1WDJN/wwNPDro
+ZXS1OHh7UKsU1tP2J9+gLiKYNBP4m4BQjEgYXpiYEoge9A1QplQ=
+=5/Ep
+-----END PGP SIGNATURE-----
index f02527a7061f05c5135263e6ff483efafd5f2af4..357b9dad2e8c2828ca75bcca4845115b453b612b 100644 (file)
@@ -1,3 +1,3 @@
 # this should be both a valid Makefile fragment and valid POSIX(ish) shell.
 
 # this should be both a valid Makefile fragment and valid POSIX(ish) shell.
 
-PERFTEST_VERSION=0.4
+PERFTEST_VERSION=0.5
index 10f127cbbef171be491b3bc8f5b742b8e8c67111..a81808b164f0cec0ec6c0010cb023fe77d7e098c 100644 (file)
@@ -137,6 +137,23 @@ detection of missing prerequisites. In the future we may treat tests
 unable to run because of missing prerequisites, but not explicitly
 skipped by the user, as failures.
 
 unable to run because of missing prerequisites, but not explicitly
 skipped by the user, as failures.
 
+Testing installed notmuch
+-------------------------
+
+Systems integrators (e.g. Linux distros) may wish to test an installed
+version of notmuch.  This can be done be running
+
+     $ NOTMUCH_TEST_INSTALLED=1 ./test/notmuch-test
+
+In this scenario the test suite does not assume a built tree, and in
+particular cannot rely on the output of 'configure'. You may want to
+set certain feature environment variables ('NOTMUCH_HAVE_*') directly
+if you know those apply to your installed notmuch). Consider also
+setting TERM=dumb if the value of TERM cannot be used (e.g. in a
+chroot with missing terminfo). Note that having a built tree may cause
+surprising/broken results for NOTMUCH_TEST_INSTALLED, so consider
+cleaning first.
+
 Writing Tests
 -------------
 The test script is written as a shell script. It is to be named as
 Writing Tests
 -------------
 The test script is written as a shell script. It is to be named as
index a2f4d93fd2652a832aad55466c602a51963dfc2f..642f918d20398c4b9b8f028183cfd10e130da5e8 100755 (executable)
@@ -66,6 +66,7 @@ test_begin_subtest 'NOTMUCH_CONFIG is set and points to an existing file'
 test_expect_success 'test -f "${NOTMUCH_CONFIG}"'
 
 test_begin_subtest 'PATH is set to build directory'
 test_expect_success 'test -f "${NOTMUCH_CONFIG}"'
 
 test_begin_subtest 'PATH is set to build directory'
+test_subtest_broken_for_installed
 test_expect_equal \
     "$(dirname ${TEST_DIRECTORY})" \
     "$(echo $PATH|cut -f1 -d: | sed -e 's,/test/valgrind/bin$,,')"
 test_expect_equal \
     "$(dirname ${TEST_DIRECTORY})" \
     "$(echo $PATH|cut -f1 -d: | sed -e 's,/test/valgrind/bin$,,')"
index da45d3aecf7ae7a0c6e86be06f6d21155c50129b..827edc149a727bb5427fcb53d8a11ab6516b9bc1 100755 (executable)
@@ -12,13 +12,16 @@ test_expect_success 'notmuch help'
 test_begin_subtest 'notmuch --version'
 test_expect_success 'notmuch --version'
 
 test_begin_subtest 'notmuch --version'
 test_expect_success 'notmuch --version'
 
-if [ $NOTMUCH_HAVE_MAN -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_MAN-0}" = "1" ]; then
     test_begin_subtest 'notmuch --help tag'
     test_expect_success 'notmuch --help tag'
 
     test_begin_subtest 'notmuch help tag'
     test_expect_success 'notmuch help tag'
 else
     test_begin_subtest 'notmuch --help tag'
     test_expect_success 'notmuch --help tag'
 
     test_begin_subtest 'notmuch help tag'
     test_expect_success 'notmuch help tag'
 else
+    if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+       test_done
+    fi
     test_begin_subtest 'notmuch --help tag (man pages not available)'
     test_expect_success 'test_must_fail notmuch --help tag >/dev/null'
 
     test_begin_subtest 'notmuch --help tag (man pages not available)'
     test_expect_success 'test_must_fail notmuch --help tag >/dev/null'
 
index 43bbce31057d4fc108621c505057618d5b0d9990..1d2b7df8def6c605fe82841abb25fe7f57edb065 100755 (executable)
@@ -3,6 +3,8 @@
 test_description='"notmuch config"'
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description='"notmuch config"'
 . $(dirname "$0")/test-lib.sh || exit 1
 
+cp notmuch-config initial-config
+
 test_begin_subtest "Get string value"
 test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
 
 test_begin_subtest "Get string value"
 test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
 
@@ -57,9 +59,12 @@ database.mail_root=MAIL_DIR
 database.path=MAIL_DIR
 foo.list=this;is another;list value;
 foo.string=this is another string value
 database.path=MAIL_DIR
 foo.list=this;is another;list value;
 foo.string=this is another string value
+index.as_text=
 maildir.synchronize_flags=true
 new.ignore=
 new.tags=unread;inbox
 maildir.synchronize_flags=true
 new.ignore=
 new.tags=unread;inbox
+search.authors_matched_separator=| 
+search.authors_separator=, 
 search.exclude_tags=
 user.name=Notmuch Test Suite
 user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
 search.exclude_tags=
 user.name=Notmuch Test Suite
 user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
@@ -192,4 +197,17 @@ test_begin_subtest "get built_with.nonexistent prints false"
 output=$(notmuch config get built_with.nonexistent)
 test_expect_equal "$output" "false"
 
 output=$(notmuch config get built_with.nonexistent)
 test_expect_equal "$output" "false"
 
+test_begin_subtest "Bad utf8 reported as error"
+cp initial-config bad-config
+printf '[query]\nq3=from:\xff\n' >>bad-config
+test_expect_code 1 "notmuch --config=./bad-config config list"
+
+test_begin_subtest "Specific error message about bad utf8"
+notmuch --config=./bad-config config list 2>ERRORS
+cat <<EOF > EXPECTED
+GLib: Key file contains key “q3” with value “from:�” which is not UTF-8
+Error: unable to load config file.
+EOF
+test_expect_equal_file EXPECTED ERRORS
+
 test_done
 test_done
index 10b29ec33f3a726f261feb5df98d58789643cf3e..39846d34b7f32f3912619bf49d30d90c4ab67f4d 100755 (executable)
@@ -21,7 +21,8 @@ baz
 EOF
 
 expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output
 EOF
 
 expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output
-test_expect_equal_file ${expected_dir}/config-with-comments new-notmuch-config
+sed '/^$/d' < new-notmuch-config > filtered-config
+test_expect_equal_file ${expected_dir}/config-with-comments filtered-config
 
 test_begin_subtest "setup consistent with config-set for single items"
 # note this relies on the config state from the previous test.
 
 test_begin_subtest "setup consistent with config-set for single items"
 # note this relies on the config state from the previous test.
index cb67889c95f82d6a4d130d65560fb453ff8d5efe..52888be22a86acdf15a83a532c5d80316b738aae 100755 (executable)
@@ -383,7 +383,22 @@ No new mail. Removed 1 message.
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "Long file names have reasonable diagnostics"
+printf -v name 'f%.0s' {1..234}
+generate_message "[filename]=$name"
+notmuch new 2>&1 | notmuch_dir_sanitize >OUTPUT
+rm ${MAIL_DIR}/${name}
+cat <<EOF > EXPECTED
+Note: Ignoring non-indexable path: MAIL_DIR/$name
+add_file: Path supplied is illegal for this function
+filename too long for file-direntry term: MAIL_DIR/$name
+Processed 1 file in almost no time.
+No new mail.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "Xapian exception: read only files"
 test_begin_subtest "Xapian exception: read only files"
+test_subtest_broken_for_root
 chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
 output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
 chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
 chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
 output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
 chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
@@ -455,12 +470,4 @@ Date: Fri, 17 Jun 2016 22:14:41 -0400
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-add_email_corpus indexing
-
-test_begin_subtest "index text/* attachments"
-test_subtest_known_broken
-notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
-notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
-test_expect_equal_file_nonempty EXPECTED OUTPUT
-
 test_done
 test_done
index fe2953247302db5d1a5da8568b5667c1a64a000c..049b745e0a6d807218ad5c0c6526b8e59116ea34 100755 (executable)
@@ -299,9 +299,12 @@ database.backup_dir
 database.hook_dir
 database.mail_root=MAIL_DIR
 database.path
 database.hook_dir
 database.mail_root=MAIL_DIR
 database.path
+index.as_text=
 maildir.synchronize_flags=true
 new.ignore=
 new.tags=unread;inbox
 maildir.synchronize_flags=true
 new.ignore=
 new.tags=unread;inbox
+search.authors_matched_separator=| 
+search.authors_separator=, 
 search.exclude_tags=
 user.name=Notmuch Test Suite
 user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
 search.exclude_tags=
 user.name=Notmuch Test Suite
 user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
@@ -324,6 +327,8 @@ database.mail_root
 database.path
 maildir.synchronize_flags
 new.tags
 database.path
 maildir.synchronize_flags
 new.tags
+search.authors_matched_separator
+search.authors_separator
 user.name
 user.other_email
 user.primary_email
 user.name
 user.other_email
 user.primary_email
@@ -373,6 +378,11 @@ EOF
           notmuch new
           test_expect_equal "$(xapian-metadata get ${XAPIAN_PATH} version)" 3
           ;;
           notmuch new
           test_expect_equal "$(xapian-metadata get ${XAPIAN_PATH} version)" 3
           ;;
+       home_mail|maildir_env)
+          test_begin_subtest "No errors from config list ($config)"
+          notmuch config list 2>OUTPUT 1>/dev/null
+          test_expect_equal_file /dev/null OUTPUT
+          ;;
        *)
           backup_database
           test_begin_subtest ".notmuch without xapian/ handled gracefully ($config)"
        *)
           backup_database
           test_begin_subtest ".notmuch without xapian/ handled gracefully ($config)"
index 4814670681165322d4769487991ef4f12542ea64..d63333b65eaa8f1a84f4051423afddcf8a403e96 100755 (executable)
@@ -132,32 +132,57 @@ restore_database
 
 test_begin_subtest "count library function is non-destructive"
 cat<<EOF > EXPECTED
 
 test_begin_subtest "count library function is non-destructive"
 cat<<EOF > EXPECTED
+== stdout ==
 1: 52 messages
 2: 52 messages
 Exclude 'spam'
 3: 52 messages
 4: 52 messages
 1: 52 messages
 2: 52 messages
 Exclude 'spam'
 3: 52 messages
 4: 52 messages
+== stderr ==
 EOF
 EOF
-test_python <<EOF
-import sys
-import notmuch
-
-query_string = 'tag:inbox or tag:spam'
-tag_string = 'spam'
-
-database = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-query = notmuch.Query(database, query_string)
-
-print("1: {} messages".format(query.count_messages()))
-print("2: {} messages".format(query.count_messages()))
-print("Exclude '{}'".format(tag_string))
-query.exclude_tag(tag_string)
-print("3: {} messages".format(query.count_messages()))
-print("4: {} messages".format(query.count_messages()))
+
+test_C ${MAIL_DIR} <<'EOF' > OUTPUT
+#include <notmuch-test.h>
+
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat = NOTMUCH_STATUS_SUCCESS;
+   char *msg = NULL;
+   notmuch_query_t *query;
+   const char *str = "tag:inbox or tag:spam";
+   const char *tag_str = "spam";
+   unsigned int count;
+
+   stat=notmuch_database_open_with_config (argv[1],
+                                      NOTMUCH_DATABASE_MODE_READ_ONLY,
+                                      NULL, NULL, &db, &msg);
+   if (stat) {
+       fprintf (stderr, "open: %s\n", msg);
+       exit(1);
+   }
+
+   EXPECT0(notmuch_query_create_with_syntax (db, str,
+                                            NOTMUCH_QUERY_SYNTAX_XAPIAN, &query));
+   EXPECT0(notmuch_query_count_messages (query, &count));
+   printf("1: %d messages\n", count);
+   EXPECT0(notmuch_query_count_messages (query, &count));
+   printf("2: %d messages\n", count);
+   printf("Exclude '%s'\n",tag_str);
+   stat = notmuch_query_add_tag_exclude (query, tag_str);
+   if (stat && stat != NOTMUCH_STATUS_IGNORED) {
+     fprintf(stderr, "status=%d\n", stat);
+     exit(1);
+   }
+   EXPECT0(notmuch_query_count_messages (query, &count));
+   printf("3: %d messages\n", count);
+   EXPECT0(notmuch_query_count_messages (query, &count));
+   printf("4: %d messages\n", count);
+}
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
 
     test_begin_subtest "and of exact terms (query=sexp)"
     output=$(notmuch count --query=sexp '(and "wonderful" "wizard")')
 
     test_begin_subtest "and of exact terms (query=sexp)"
     output=$(notmuch count --query=sexp '(and "wonderful" "wizard")')
index a3f0dead1420239fd654c89ea04531d1ee141729..515eb88f34b3f9c69bca5b13cd6c5cc9c1b8c44d 100755 (executable)
@@ -34,6 +34,12 @@ add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"
 output=$(notmuch search id:${gen_msg_id} | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
 
 output=$(notmuch search id:${gen_msg_id} | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
 
+test_begin_subtest "Message-Id with spaces"
+test_subtest_known_broken
+add_message '[id]="message id@example.com"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --output=messages id:"message id@example.com")
+test_expect_equal "$output" "messageid@example.com"
+
 test_begin_subtest "Search by mid:"
 add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
 output=$(notmuch search mid:${gen_msg_id} | notmuch_search_sanitize)
 test_begin_subtest "Search by mid:"
 add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
 output=$(notmuch search mid:${gen_msg_id} | notmuch_search_sanitize)
@@ -132,6 +138,7 @@ thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; Message-Id with spaces (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)
index 0c7db9c248bdfe2e143dab1f9f63d9ceaded9256..8800b545982751c1ac5dc0716fa605f4c72fe77c 100755 (executable)
@@ -2,7 +2,7 @@
 test_description='"notmuch search" in several variations'
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description='"notmuch search" in several variations'
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ $NOTMUCH_HAVE_SFSEXP -ne 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" != "1" ]; then
     printf "Skipping due to missing sfsexp library\n"
     test_done
 fi
     printf "Skipping due to missing sfsexp library\n"
     test_done
 fi
index 8bb3627ac1f0e39307d134548b4a2122ddcba51d..0cafbe20f11a0140cac8df350d6e9900ec52251e 100755 (executable)
@@ -325,7 +325,7 @@ cat <<EOF >EXPECTED
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-if [[ NOTMUCH_HAVE_SFSEXP = 1 ]]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
     test_begin_subtest "sexpr query: all messages"
     notmuch address '*' > EXPECTED
     notmuch address --query=sexp '()' > OUTPUT
     test_begin_subtest "sexpr query: all messages"
     notmuch address '*' > EXPECTED
     notmuch address --query=sexp '()' > OUTPUT
diff --git a/test/T131-show-limiting.sh b/test/T131-show-limiting.sh
new file mode 100755 (executable)
index 0000000..30d1f25
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+test_description='"notmuch show" --offset and --limit parameters'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+show () {
+    local kind="$1"
+    shift
+    if [ "$kind" = messages ]; then
+        set -- --unthreaded "$@"
+    fi
+    notmuch show --body=false --format=text --entire-thread=false "$@" "*" |
+        sed -nre 's/^.message\{.*\<depth:0\>.*/&/p'
+}
+
+for outp in messages threads; do
+    test_begin_subtest "$outp: limit does the right thing"
+    show $outp | head -n 20 >expected
+    show $outp --limit=20 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: concatenation of limited shows"
+    show $outp | head -n 20 >expected
+    show $outp --limit=10 >output
+    show $outp --limit=10 --offset=10 >>output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: limit larger than result set"
+    N=$(notmuch count --output=$outp "*")
+    show $outp >expected
+    show $outp --limit=$((1 + N)) >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: limit = 0"
+    test_expect_equal "$(show $outp --limit=0)" ""
+
+    test_begin_subtest "$outp: offset does the right thing"
+    # note: tail -n +N is 1-based
+    show $outp | tail -n +21 >expected
+    show $outp --offset=20 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: offset = 0"
+    show $outp >expected
+    show $outp --offset=0 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: negative offset"
+    show $outp | tail -n 20 >expected
+    show $outp --offset=-20 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: negative offset"
+    show $outp | tail -n 1 >expected
+    show $outp --offset=-1 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: negative offset combined with limit"
+    show $outp | tail -n 20 | head -n 10 >expected
+    show $outp --offset=-20 --limit=10 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: negative offset combined with equal limit"
+    show $outp | tail -n 20 >expected
+    show $outp --offset=-20 --limit=20 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: negative offset combined with large limit"
+    show $outp | tail -n 20 >expected
+    show $outp --offset=-20 --limit=50 >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "$outp: negative offset larger than results"
+    N=$(notmuch count --output=$outp "*")
+    show $outp >expected
+    show $outp --offset=-$((1 + N)) >output
+    test_expect_equal_file expected output
+done
+
+test_done
index 1a2fd77e4853708580293c9695a61cab0398fe8e..273c0af35927e092c37b7a9c47cacfc9b8aaac89 100755 (executable)
@@ -320,6 +320,7 @@ test_begin_subtest "Tag name beginning with -"
 test_expect_code 1 'notmuch tag +- One'
 
 test_begin_subtest "Xapian exception: read only files"
 test_expect_code 1 'notmuch tag +- One'
 
 test_begin_subtest "Xapian exception: read only files"
+test_subtest_broken_for_root
 chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
 output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
 chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
 chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
 output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
 chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
@@ -327,7 +328,7 @@ test_expect_equal "$output" "A Xapian exception occurred opening database"
 
 add_email_corpus
 
 
 add_email_corpus
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
 
     test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
     test_query_syntax '(or "php" "wizard")' 'php or wizard'
 
     test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
     test_query_syntax '(or "php" "wizard")' 'php or wizard'
index 4a797f6aad14dfe13c18c8a69d3eac617dcee80e..318c97880150d12dcb36f312c5794a04e3898af8 100755 (executable)
@@ -65,8 +65,9 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
  \"tags\": [\"inbox\",
  \"unread\"]}]"
 
  \"tags\": [\"inbox\",
  \"unread\"]}]"
 
+if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
 test_begin_subtest "Search message: json, 64-bit timestamp"
 test_begin_subtest "Search message: json, 64-bit timestamp"
-if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then
+if [ "${NOTMUCH_HAVE_64BIT_TIME_T-0}" != "1" ]; then
     test_subtest_known_broken
 fi
 add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\""
     test_subtest_known_broken
 fi
 add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\""
@@ -81,6 +82,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
  \"query\": [\"id:$gen_msg_id\", null],
  \"tags\": [\"inbox\",
  \"unread\"]}]"
  \"query\": [\"id:$gen_msg_id\", null],
  \"tags\": [\"inbox\",
  \"unread\"]}]"
+fi # NOTMUCH_TEST_INSTALLED undefined / empty
 
 test_begin_subtest "Format version: too low"
 test_expect_code 20 "notmuch search --format-version=0 \\*"
 
 test_begin_subtest "Format version: too low"
 test_expect_code 20 "notmuch search --format-version=0 \\*"
diff --git a/test/T206-author-separator.sh b/test/T206-author-separator.sh
new file mode 100755 (executable)
index 0000000..b8b1afb
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+test_description="custom separator for authors"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Adding parent message"
+generate_message [body]=findme [id]=new-parent-id [subject]=author-reorder-threadtest '[from]="User <user@example.com>"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Adding initial child message"
+generate_message [body]=findme "[in-reply-to]=\<new-parent-id\>" [subject]=author-reorder-threadtest '[from]="last name, first name <user1@example.com>"' '[date]="Sat, 01 Jan 2000 12:01:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Adding second child message"
+generate_message [body]=findme "[in-reply-to]=\<new-parent-id\>" [subject]=author-reorder-threadtest '[from]="first last <user2@example.com>"' '[date]="Sat, 01 Jan 2000 12:02:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Custom separarator is used"
+notmuch config set search.authors_matched_separator "#"
+notmuch config set search.authors_separator ";"
+output=$(notmuch search from:user | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/3] User#last name, first name;first last; author-reorder-threadtest (inbox unread)"
+
+test_done
index 207f578810133643491dad884fafb879a8e62312..120d7138d61809fd46cb724501bce921de13db87 100755 (executable)
@@ -24,7 +24,7 @@ test_begin_subtest "Basic reply"
 notmuch reply id:${gen_msg_id} >OUTPUT 2>&1 && echo OK >> OUTPUT
 test_expect_equal_file basic.expected OUTPUT
 
 notmuch reply id:${gen_msg_id} >OUTPUT 2>&1 && echo OK >> OUTPUT
 test_expect_equal_file basic.expected OUTPUT
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
 
     test_begin_subtest "Basic reply (query=sexp)"
     notmuch reply --query=sexp "(id ${gen_msg_id})" >OUTPUT 2>&1 && echo OK >> OUTPUT
 
     test_begin_subtest "Basic reply (query=sexp)"
     notmuch reply --query=sexp "(id ${gen_msg_id})" >OUTPUT 2>&1 && echo OK >> OUTPUT
index a86f0fb7e8f0440de6268075e91ba3b4262a38b1..c3f188398b87e6943d6e3a568191ed8303431605 100755 (executable)
@@ -118,7 +118,7 @@ notmuch dump -- from:cworth > dump-dash-cworth.actual
 test_expect_equal_file dump-cworth.expected dump-dash-cworth.actual
 
 
 test_expect_equal_file dump-cworth.expected dump-dash-cworth.actual
 
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
 
     test_begin_subtest "dump --query=sexp -- '(from cworth)'"
     notmuch dump --query=sexp -- '(from cworth)' > dump-dash-cworth.actual2
 
     test_begin_subtest "dump --query=sexp -- '(from cworth)'"
     notmuch dump --query=sexp -- '(from cworth)' > dump-dash-cworth.actual2
@@ -139,6 +139,7 @@ notmuch dump --output=dump-outfile-dash-inbox.actual -- from:cworth
 test_expect_equal_file dump-cworth.expected dump-outfile-dash-inbox.actual
 
 test_begin_subtest "Check for a safe set of message-ids"
 test_expect_equal_file dump-cworth.expected dump-outfile-dash-inbox.actual
 
 test_begin_subtest "Check for a safe set of message-ids"
+test_subtest_broken_for_installed
 notmuch search --output=messages from:cworth | sed s/^id:// > EXPECTED
 notmuch search --output=messages from:cworth | sed s/^id:// |\
        $TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT
 notmuch search --output=messages from:cworth | sed s/^id:// > EXPECTED
 notmuch search --output=messages from:cworth | sed s/^id:// |\
        $TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT
@@ -246,6 +247,7 @@ 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'
 test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
 
 test_begin_subtest 'format=batch-tag, checking encoded output'
+test_subtest_broken_for_installed
 NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
         awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
 NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth > OUTPUT.$test_count
 NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
         awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
 NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth > OUTPUT.$test_count
index 9d0df187da73af4c4319a621442e6342f728bad2..e96c16013443f57eab16ae55581a73d4256e9dee 100755 (executable)
@@ -258,6 +258,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Verify that sent messages are saved/searchable (via FCC)"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Verify that sent messages are saved/searchable (via FCC)"
+test_subtest_broken_for_installed
 notmuch new > /dev/null
 output=$(notmuch search 'subject:"testing message sent via SMTP"' | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; Testing message sent via SMTP (inbox)"
 notmuch new > /dev/null
 output=$(notmuch search 'subject:"testing message sent via SMTP"' | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; Testing message sent via SMTP (inbox)"
@@ -350,6 +351,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Reply within emacs"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "Reply within emacs"
+test_subtest_broken_for_installed
 test_emacs '(let ((message-hidden-headers ''()))
            (notmuch-search "subject:\"testing message sent via SMTP\"")
            (notmuch-test-wait)
 test_emacs '(let ((message-hidden-headers ''()))
            (notmuch-search "subject:\"testing message sent via SMTP\"")
            (notmuch-test-wait)
@@ -1030,7 +1032,7 @@ End of search results.
 YYY/notmuch_fail exited with status 1 (see *Notmuch errors* for more details)
 === ERROR ===
 YYY/notmuch_fail exited with status 1
 YYY/notmuch_fail exited with status 1 (see *Notmuch errors* for more details)
 === ERROR ===
 YYY/notmuch_fail exited with status 1
-command: YYY/notmuch_fail search --format\=sexp --format-version\=5 --sort\=newest-first tag\:inbox
+command: YYY/notmuch_fail search --format\=sexp --format-version\=5 --sort\=newest-first --exclude\=false tag\:inbox
 exit status: 1"
 
 test_begin_subtest "Search handles subprocess warnings"
 exit status: 1"
 
 test_begin_subtest "Search handles subprocess warnings"
diff --git a/test/T312-emacs-hl-line.sh b/test/T312-emacs-hl-line.sh
new file mode 100755 (executable)
index 0000000..3402811
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+test_description="emacs hl-line-mode"
+. $(dirname "$0")/test-lib.sh || exit 1
+. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
+
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
+
+test_require_emacs
+add_email_corpus
+
+test_begin_subtest "line 1, search"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-search "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                        (let ((start (overlay-start hl-line-overlay))
+                              (end (overlay-end hl-line-overlay)))
+                          (list start (> end start)))
+                        (list 1 t)))'
+
+test_begin_subtest "line 1, tree"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                        (let ((start (overlay-start hl-line-overlay))
+                              (end (overlay-end hl-line-overlay)))
+                          (list start (> end start)))
+                        (list 1 t)))'
+
+test_begin_subtest "line 1, unthreaded"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                        (let ((start (overlay-start hl-line-overlay))
+                              (end (overlay-end hl-line-overlay)))
+                          (list start (> end start)))
+                        (list 1 t)))'
+
+test_begin_subtest "line 1, search, refresh"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-search "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-refresh-this-buffer)
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal (overlay-start hl-line-overlay) 1))'
+
+test_begin_subtest "line 1, tree, refresh"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-refresh-this-buffer)
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                        (let ((start (overlay-start hl-line-overlay))
+                              (end (overlay-end hl-line-overlay)))
+                          (list start (> end start)))
+                        (list 1 t)))'
+
+test_begin_subtest "line 1, unthreaded, refresh"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-refresh-this-buffer)
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                        (let ((start (overlay-start hl-line-overlay))
+                              (end (overlay-end hl-line-overlay)))
+                          (list start (> end start)))
+                        (list 1 t)))'
+
+
+test_begin_subtest "line 12, notmuch-search"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-search "tag:inbox")
+                       (notmuch-test-wait)
+                       (forward-line 11)
+                       (notmuch-post-command)
+                       (notmuch-test-expect-equal
+                          (line-number-at-pos (overlay-start hl-line-overlay)) 12))'
+
+test_begin_subtest "line 12, tree"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (forward-line 11)
+                       (notmuch-post-command)
+                       (notmuch-test-expect-equal
+                          (line-number-at-pos (overlay-start hl-line-overlay)) 12))'
+
+test_begin_subtest "line 12, unthreaded"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-unthreaded "tag:inbox")
+                       (notmuch-test-wait)
+                       (forward-line 11)
+                       (notmuch-post-command)
+                       (notmuch-test-expect-equal
+                          (line-number-at-pos (overlay-start hl-line-overlay)) 12))'
+
+test_begin_subtest "line 12, search, refresh"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-search "tag:inbox")
+                       (notmuch-test-wait)
+                       (forward-line 11)
+                       (notmuch-post-command)
+                       (notmuch-refresh-this-buffer)
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                          (line-number-at-pos (overlay-start hl-line-overlay)) 12))'
+
+test_begin_subtest "line 12, tree, refresh"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (forward-line 11)
+                       (notmuch-post-command)
+                       (notmuch-refresh-this-buffer)
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                          (line-number-at-pos (overlay-start hl-line-overlay)) 12))'
+
+test_begin_subtest "line 12, unthreaded, refresh"
+test_emacs_expect_t '(let ((notmuch-hl-line t))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (forward-line 11)
+                       (notmuch-post-command)
+                       (notmuch-refresh-this-buffer)
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                          (line-number-at-pos (overlay-start hl-line-overlay)) 12))'
+
+test_begin_subtest "hl-line disabled, search"
+test_emacs_expect_t '(let ((notmuch-hl-line nil))
+                       (notmuch-search "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                          (or hl-line-mode hl-line-overlay)
+                          nil))'
+
+test_begin_subtest "hl-line disabled, tree"
+test_emacs_expect_t '(let ((notmuch-hl-line nil))
+                       (notmuch-tree "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                          (or hl-line-mode hl-line-overlay)
+                          nil))'
+
+test_begin_subtest "hl-line disabled, unthreaded"
+test_emacs_expect_t '(let ((notmuch-hl-line nil))
+                       (notmuch-unthreaded "tag:inbox")
+                       (notmuch-test-wait)
+                       (notmuch-test-expect-equal
+                          (or hl-line-mode hl-line-overlay)
+                          nil))'
+
+test_done
+
index 721cbfdd60f24f425a69c2211336d3bc6fc0bd44..712a0c07b9cdf3c1f429ba3ba8298f10abef7eb0 100755 (executable)
@@ -30,6 +30,7 @@ msg_file=$(notmuch search --output=files subject:signed-message-sent-via-SMTP)
 test_expect_equal_message_body sent_message "$msg_file"
 
 test_begin_subtest "signed part content-type indexing"
 test_expect_equal_message_body sent_message "$msg_file"
 
 test_begin_subtest "signed part content-type indexing"
+test_subtest_broken_for_installed
 notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize > OUTPUT
 cat <<EOF >EXPECTED
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)
 notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize > OUTPUT
 cat <<EOF >EXPECTED
 thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)
@@ -452,6 +453,7 @@ y
     | gpg --no-tty --quiet --import
 output=$(notmuch show --format=json --verify subject:"test signed message 001" \
     | notmuch_json_show_sanitize \
     | gpg --no-tty --quiet --import
 output=$(notmuch show --format=json --verify subject:"test signed message 001" \
     | notmuch_json_show_sanitize \
+    | sed -e 's/"key-\(revoked\|missing\)"/"key-revoked"/g' \
     | sed -e 's|"created": [1234567890]*|"created": 946728000|')
 expected='[[[{"id": "XXXXX",
  "match": true,
     | sed -e 's|"created": [1234567890]*|"created": 946728000|')
 expected='[[[{"id": "XXXXX",
  "match": true,
index 809274ce14a2156ab56d68c0d0e524da3baecab6..d2118c0492efb9e3bd9bedf639b9a4f859a30ea5 100755 (executable)
@@ -183,8 +183,9 @@ test_begin_subtest "show PKCS#7 SignedData outputs valid JSON"
 output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
 test_valid_json "$output"
 
 output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
 test_valid_json "$output"
 
+if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
 test_begin_subtest "Verify signature on PKCS#7 SignedData message"
 test_begin_subtest "Verify signature on PKCS#7 SignedData message"
-if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then
+if [ "${NOTMUCH_HAVE_64BIT_TIME_T-0}" != "1" ]; then
     test_subtest_known_broken
 fi
 output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
     test_subtest_known_broken
 fi
 output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
@@ -194,6 +195,7 @@ test_json_nodes <<<"$output" \
                 'expires:[0][0][0]["crypto"]["signed"]["status"][0]["expires"]=2611032858' \
                 'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
                 'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"'
                 'expires:[0][0][0]["crypto"]["signed"]["status"][0]["expires"]=2611032858' \
                 'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
                 'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"'
+fi # NOTMUCH_TEST_INSTALLED undefined / empty
 
 test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID"
 if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then
 
 test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID"
 if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then
index 642457bf2c1f3220e7840631c76174e4a9cf9790..ff06ff691b785c96bbe5762a247868d663685864 100755 (executable)
@@ -11,6 +11,10 @@ test_description='exception symbol hiding'
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 test_begin_subtest 'running test' run_test
 mkdir -p ${PWD}/fakedb/.notmuch
 $TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \
 test_begin_subtest 'running test' run_test
 mkdir -p ${PWD}/fakedb/.notmuch
 $TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \
diff --git a/test/T390-python.sh b/test/T390-python.sh
deleted file mode 100755 (executable)
index 9f71ce3..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/usr/bin/env bash
-test_description="python bindings"
-. $(dirname "$0")/test-lib.sh || exit 1
-
-test_require_external_prereq ${NOTMUCH_PYTHON}
-
-add_email_corpus
-add_gnupg_home
-
-test_begin_subtest "compare thread ids"
-test_python <<EOF
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-q_new = notmuch.Query(db, 'tag:inbox')
-q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
-for t in q_new.search_threads():
-    print (t.get_thread_id())
-EOF
-notmuch search --sort=oldest-first --output=threads tag:inbox | sed s/^thread:// > EXPECTED
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "compare message ids"
-test_python <<EOF
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-q_new = notmuch.Query(db, 'tag:inbox')
-q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
-for m in q_new.search_messages():
-    print (m.get_message_id())
-EOF
-notmuch search --sort=oldest-first --output=messages tag:inbox | sed s/^id:// > EXPECTED
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "get non-existent file"
-test_python <<EOF
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-print (db.find_message_by_filename("i-dont-exist"))
-EOF
-test_expect_equal "$(cat OUTPUT)" "None"
-
-test_begin_subtest "get revision"
-test_python ${MAIL_DIR} <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-(revision, uuid) = db.get_revision()
-print ("%s\t%lu" % (uuid, revision))
-EOF
-notmuch_uuid_sanitize < OUTPUT > CLEAN
-cat <<'EOF' >EXPECTED
-UUID   53
-EOF
-test_expect_equal_file EXPECTED CLEAN
-
-grep '^[0-9a-f]' OUTPUT > INITIAL_OUTPUT
-
-test_begin_subtest "output of count matches test code"
-notmuch count --lastmod '*' | cut -f2-3 > OUTPUT
-test_expect_equal_file INITIAL_OUTPUT OUTPUT
-add_message '[content-type]="text/plain; charset=iso-8859-2"' \
-            '[content-transfer-encoding]=8bit' \
-            '[subject]="ISO-8859-2 encoded message"' \
-            "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences
-test_begin_subtest "Add ISO-8859-2 encoded message, call get_message_parts"
-test_python <<EOF
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-q_new = notmuch.Query(db, 'ISO-8859-2 encoded message')
-for m in q_new.search_messages():
-    for mp in m.get_message_parts():
-      continue
-    print(m.get_message_id())
-EOF
-
-notmuch search --sort=oldest-first --output=messages "tučňáččí" | sed s/^id:// > EXPECTED
-test_expect_equal_file EXPECTED OUTPUT
-
-# TODO currently these tests for setting and getting config values are
-# somewhat interdependent.  This is because the config values stored in the
-# database are not cleaned up after each test, so they remain there for the
-# next test.  The ./README file states that this can happen so it seems kind
-# of ok.
-
-test_begin_subtest "set and get config values"
-test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-db.set_config('testkey1', 'testvalue1')
-db.set_config('testkey2', 'testvalue2')
-v1 = db.get_config('testkey1')
-v2 = db.get_config('testkey2')
-print('testkey1 = ' + v1)
-print('testkey2 = ' + v2)
-EOF
-cat <<'EOF' >EXPECTED
-testkey1 = testvalue1
-testkey2 = testvalue2
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "get_configs with no match returns empty generator"
-test_python <<'EOF'
-import notmuch
-db = notmuch.Database()
-v = db.get_configs('nonexistent')
-print(list(v) == [])
-EOF
-test_expect_equal "$(cat OUTPUT)" "True"
-
-test_begin_subtest "get_configs with no arguments returns all pairs"
-test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-db.set_config("zzzafter", "afterval")
-db.set_config("aaabefore", "beforeval")
-v = db.get_configs()
-for index, keyval in enumerate(v):
-    key, val = keyval
-    print('{}: {} => {}'.format(index, key, val))
-EOF
-cat <<'EOF' >EXPECTED
-0: aaabefore => beforeval
-1: testkey1 => testvalue1
-2: testkey2 => testvalue2
-3: zzzafter => afterval
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "get_configs prefix is used to match keys"
-test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-db.set_config('testkey1', 'testvalue1')
-db.set_config('testkey2', 'testvalue2')
-v = db.get_configs('testkey')
-for index, keyval in enumerate(v):
-    key, val = keyval
-    print('{}: {} => {}'.format(index, key, val))
-EOF
-cat <<'EOF' >EXPECTED
-0: testkey1 => testvalue1
-1: testkey2 => testvalue2
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "set_config with no value will unset config entries"
-test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-db.set_config('testkey1', '')
-db.set_config('testkey2', '')
-db.set_config("zzzafter", '')
-db.set_config("aaabefore", '')
-v = db.get_configs()
-print(list(v) == [])
-EOF
-test_expect_equal "$(cat OUTPUT)" "True"
-
-mkdir -p "${MAIL_DIR}/cur"
-fname="${MAIL_DIR}/cur/simplemsg.eml"
-cat <<EOF > "$fname"
-From: test_suite@notmuchmail.org
-To: test_suite@notmuchmail.org
-Subject: encrypted message
-Date: Sat, 01 Jan 2000 12:00:00 +0000
-Message-ID: <simplemsg@crypto.notmuchmail.org>
-MIME-Version: 1.0
-Content-Type: multipart/encrypted; boundary="=-=-=";
-       protocol="application/pgp-encrypted"
-
---=-=-=
-Content-Type: application/pgp-encrypted
-
-Version: 1
-
---=-=-=
-Content-Type: application/octet-stream
-
-$(printf 'Content-Type: text/plain\n\nThis is the sekrit message\n' | gpg --no-tty --batch --quiet --trust-model=always --encrypt --armor --recipient test_suite@notmuchmail.org)
---=-=-=--
-EOF
-
-test_begin_subtest "index message with decryption"
-test_python <<EOF
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-(m, status) = db.index_file('$fname', decrypt_policy=notmuch.Database.DECRYPTION_POLICY.TRUE)
-if status == notmuch.errors.STATUS.DUPLICATE_MESSAGE_ID:
-   print("got duplicate message")
-q_new = notmuch.Query(db, 'sekrit')
-for m in q_new.search_messages():
-    print(m.get_filename())
-EOF
-echo "$fname" > EXPECTED
-test_expect_equal_file EXPECTED OUTPUT
-
-test_done
index 30872af0b9816a4e00da45a5f63b74844bcc84b2..0059b050c718a1452ae994e32d605d370e18c65b 100755 (executable)
@@ -2,10 +2,13 @@
 test_description="python bindings (pytest)"
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description="python bindings (pytest)"
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
     test_done
 fi
 
     test_done
 fi
 
+if [ "${NOTMUCH_HAVE_PYTHON3_CFFI-0}" = "0" -o "${NOTMUCH_HAVE_PYTHON3_PYTEST-0}" = "0" ]; then
+    test_done
+fi
 
 test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
 pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
 
 test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
 pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
index 15c8fc6bbe0b7dd76830df509678ebd35d235228..061612193cbe8d3f8373e7a4fe26bfddc9e41359 100755 (executable)
@@ -2,7 +2,7 @@
 test_description="python bindings (notmuch test suite)"
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description="python bindings (notmuch test suite)"
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then
+if [ "${NOTMUCH_HAVE_PYTHON3_CFFI-0}" = "0" -o "${NOTMUCH_HAVE_PYTHON3_PYTEST-0}" = "0" ]; then
     test_done
 fi
 
     test_done
 fi
 
index e828efed3dbe4bca31ccf0f134c560b021cea928..d0c6bb17bdda217e1e4aff594f77592fa04636d8 100755 (executable)
@@ -2,7 +2,7 @@
 test_description="ruby bindings"
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description="ruby bindings"
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ "${NOTMUCH_HAVE_RUBY_DEV}" = "0" ]; then
+if [ -z "${NOTMUCH_TEST_INSTALLED-}" -a "${NOTMUCH_HAVE_RUBY_DEV-0}" = "0" ]; then
     test_subtest_missing_external_prereq_["ruby development files"]=t
 fi
 
     test_subtest_missing_external_prereq_["ruby development files"]=t
 fi
 
@@ -12,10 +12,14 @@ test_ruby() {
     (
        cat <<-EOF
        require 'notmuch'
     (
        cat <<-EOF
        require 'notmuch'
-       db = Notmuch::Database.new('$MAIL_DIR')
+       db = Notmuch::Database.new()
        EOF
        cat
        EOF
        cat
-    ) | $NOTMUCH_RUBY -I "$NOTMUCH_BUILDDIR/bindings/ruby"> OUTPUT
+    ) | if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+       ruby
+    else
+       $NOTMUCH_RUBY -I "$NOTMUCH_BUILDDIR/bindings/ruby"
+    fi> OUTPUT
     test_expect_equal_file EXPECTED OUTPUT
 }
 
     test_expect_equal_file EXPECTED OUTPUT
 }
 
index d9aa8e2d01c811b77a0373cde8105e6e32e29965..40b625feddbfce84db3934ab8db429469efcf7d0 100755 (executable)
@@ -2,6 +2,10 @@
 test_description="argument parsing"
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description="argument parsing"
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 test_begin_subtest "sanity check"
 $TEST_DIRECTORY/arg-test pos1 --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
 cat <<EOF > EXPECTED
 test_begin_subtest "sanity check"
 $TEST_DIRECTORY/arg-test pos1 --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
 cat <<EOF > EXPECTED
index 559df8aaea3f15d73fab7e1f5b4d6739de130a20..ff340f0603479576ae725ccf43bd33aca3d8a3f0 100755 (executable)
@@ -318,6 +318,54 @@ test_expect_equal "$(cat MESSAGES)" "COMPLETE"
 
 add_email_corpus attachment
 
 
 add_email_corpus attachment
 
+test_begin_subtest "display of text/calendar"
+test_emacs '(let ((notmuch-show-all-multipart/alternative-parts t))
+          (notmuch-show "id:YT3PR01MB10572EFC9F7C81F9446214768CEF72@YT3PR01MB10572.CANPRD01.PROD.OUTLOOK.COM"))
+       (test-visible-output "OUTPUT")'
+cat <<EOF > EXPECTED
+David Bremner <bremner@example.com> (1970-01-01) (inbox)
+Subject: test
+To: "david@tethera.net" <david@tethera.net>
+Date: Thu, 01 Jan 1970 00:00:00 +0000
+
+[ multipart/alternative ]
+[ text/plain ]
+This meeting will could have been an email
+[ text/calendar ]
+%%(and (diary-cyclic 1 5 27 2024) (diary-block 5 27 2024 8 27 2024)) 17:00-17:30 test [In-person]
+ Desc: This meeting will could have been an email
+
+
+ Organizer: mailto:bremner@example.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "display of text/calendar, symlinked tmpdir"
+mkdir real-tmp
+ln -s real-tmp tmp
+test_emacs "(let ((notmuch-show-all-multipart/alternative-parts t)
+                 (temporary-file-directory \"tmp\")
+                 (find-file-visit-truename t))
+            (notmuch-show \"id:YT3PR01MB10572EFC9F7C81F9446214768CEF72@YT3PR01MB10572.CANPRD01.PROD.OUTLOOK.COM\"))
+           (test-visible-output \"OUTPUT\")"
+cat <<EOF > EXPECTED
+David Bremner <bremner@example.com> (1970-01-01) (inbox)
+Subject: test
+To: "david@tethera.net" <david@tethera.net>
+Date: Thu, 01 Jan 1970 00:00:00 +0000
+
+[ multipart/alternative ]
+[ text/plain ]
+This meeting will could have been an email
+[ text/calendar ]
+%%(and (diary-cyclic 1 5 27 2024) (diary-block 5 27 2024 8 27 2024)) 17:00-17:30 test [In-person]
+ Desc: This meeting will could have been an email
+
+
+ Organizer: mailto:bremner@example.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "tar not inlined by default"
 test_emacs '(notmuch-show "id:874llc2bkp.fsf@curie.anarc.at")
        (test-visible-output "OUTPUT")'
 test_begin_subtest "tar not inlined by default"
 test_emacs '(notmuch-show "id:874llc2bkp.fsf@curie.anarc.at")
        (test-visible-output "OUTPUT")'
@@ -412,4 +460,19 @@ subject4=$(grep '^Subject:' $FILE4)
 subject=$(grep '^Subject:' OUTPUT)
 test_expect_equal "$subject4" "$subject"
 
 subject=$(grep '^Subject:' OUTPUT)
 test_expect_equal "$subject4" "$subject"
 
+test_begin_subtest "notmuch-show for message with subject with embedded CRNL"
+add_message "[subject]=\"=?UTF-8?B?8J+Pi++4jw==?= A SALE to boost your =?UTF-8?Q?workout=0D=0A?=\" [body]=the-message-body" 
+test_emacs "(notmuch-show \"id:${gen_msg_id}\")
+           (test-output \"OUTPUT.raw\")"
+cat <<EOF >EXPECTED
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
+Subject: 🏋️ A SALE to boost your workout 
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+
+the-message-body
+EOF
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
 test_done
index 3a1c449ef2b4350dd7d08397b5213ea1efb526fc..4243f65af07d837d5d9b92e4870c6b8c7b04746b 100755 (executable)
@@ -100,7 +100,7 @@ test_emacs '(notmuch-hello)
            (notmuch-test-wait)
            (test-output)
            (delete-other-windows)'
            (notmuch-test-wait)
            (test-output)
            (delete-other-windows)'
-test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-oldest-first OUTPUT
 
 test_begin_subtest "Tree view of a single thread (from search)"
 test_emacs '(notmuch-hello)
 
 test_begin_subtest "Tree view of a single thread (from search)"
 test_emacs '(notmuch-hello)
@@ -200,6 +200,36 @@ test_emacs '(test-log-error
                (notmuch-tree "*")))'
 test_expect_equal "$(cat MESSAGES)" "COMPLETE"
 
                (notmuch-tree "*")))'
 test_expect_equal "$(cat MESSAGES)" "COMPLETE"
 
+# reinitialize database for outline tests
+add_email_corpus
+
+test_begin_subtest "start in outline mode"
+test_emacs '(let ((notmuch-tree-outline-enabled t))
+       (notmuch-tree "tag:inbox")
+       (notmuch-test-wait)
+       (test-visible-output))'
+# folding all messages by height or depth should look the same
+test_expect_equal_file $EXPECTED/inbox-outline OUTPUT
+
+test_begin_subtest "outline-cycle-buffer"
+test_emacs '(let ((notmuch-tree-outline-enabled t))
+       (notmuch-tree "tag:inbox")
+       (notmuch-test-wait)
+       (outline-cycle-buffer)
+       (outline-cycle-buffer)
+       (notmuch-test-wait)
+       (test-visible-output))'
+# folding all messages by height or depth should look the same
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+
+test_begin_subtest "notmuch-tree for message with subject with embedded CRNL"
+add_message "[subject]=\"=?UTF-8?B?8J+Pi++4jw==?= A SALE to boost your =?UTF-8?Q?workout=0D=0A?=\" [body]=the-message-body" 
+test_emacs "(notmuch-tree \"id:${gen_msg_id}\")
+           (notmuch-test-wait)
+           (test-output)"
+# one line of output, plus "End of search results."
+test_expect_equal "$(wc -l < OUTPUT)" 2
+
 add_email_corpus duplicate
 
 ID3=87r2ecrr6x.fsf@zephyr.silentflame.com
 add_email_corpus duplicate
 
 ID3=87r2ecrr6x.fsf@zephyr.silentflame.com
diff --git a/test/T461-emacs-search-exclude.sh b/test/T461-emacs-search-exclude.sh
new file mode 100755 (executable)
index 0000000..47f7468
--- /dev/null
@@ -0,0 +1,185 @@
+#!/usr/bin/env bash
+
+test_description="exclude options persist between Emacs search and tree modes"
+. $(dirname "$0")/test-lib.sh || exit 1
+. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
+
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs-exclude.expected-output
+
+test_require_emacs
+add_email_corpus
+notmuch config set search.exclude_tags deleted
+notmuch tag +deleted -- 'from:"Stewart Smith"' or 'from:"Chris Wilson"'
+
+# Basic test cases just asserting exclude option is working and consistent.
+
+test_begin_subtest "Search doesn't contain excluded mail by default"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox-without-excluded OUTPUT
+
+test_begin_subtest "Toggling exclude in search will show excluded mail"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Tree search doesn't contain excluded mail by default"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-without-excluded OUTPUT
+
+test_begin_subtest "Toggling exclude in tree search will show excluded mail"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (notmuch-tree-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Unthreaded search doesn't contain excluded mail by default"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-unthreaded-from-search-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-unthreaded-tag-inbox-without-excluded OUTPUT
+
+test_begin_subtest "Toggling exclude in unthreaded will show excluded mail"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-unthreaded-from-search-current-query)
+           (notmuch-test-wait)
+           (notmuch-tree-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-unthreaded-tag-inbox-with-excluded OUTPUT
+
+# Cycling from search to tree to unthreaded and vice versa will persist the current
+# value of notmuch-search-hide-excluded.
+
+test_begin_subtest "Value of hide-excluded from search persists into tree search"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Value of hide-excluded from search persists into unthreaded"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (notmuch-unthreaded-from-search-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-unthreaded-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Value of hide-excluded from tree persists into search"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (notmuch-tree-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (notmuch-search-from-tree-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Value of hide-excluded from tree persists into unthreaded"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (notmuch-tree-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (notmuch-unthreaded-from-tree-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-unthreaded-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Value of hide-excluded from unthreaded persists into tree"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-unthreaded-from-search-current-query)
+           (notmuch-test-wait)
+           (notmuch-tree-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (notmuch-tree-from-unthreaded-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-with-excluded OUTPUT
+
+test_begin_subtest "Value of hide-excluded from unthreaded persists into search"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-unthreaded-from-search-current-query)
+           (notmuch-test-wait)
+           (notmuch-tree-toggle-hide-excluded)
+           (notmuch-test-wait)
+           (notmuch-search-from-tree-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox-with-excluded OUTPUT
+
+test_done
index b2eb80b901027ae2931d06f6ef9be33dac04722c..8bddf3e744c7f58d4db165b15905ccc83921b12c 100755 (executable)
@@ -2,6 +2,10 @@
 test_description="hex encoding and decoding"
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description="hex encoding and decoding"
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 test_begin_subtest "round trip"
 find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
 $TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT
 test_begin_subtest "round trip"
 find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
 $TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT
index f89755ed0e42cfe7d87040f85410e77b8002adfd..3b6e48c42aefe040ec926c1e634bab3c9ffbc7a0 100755 (executable)
@@ -2,6 +2,10 @@
 test_description="date/time parser module"
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description="date/time parser module"
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 # Sanity/smoke tests for the date/time parser independent of notmuch
 
 _date () {
 # Sanity/smoke tests for the date/time parser independent of notmuch
 
 _date () {
index c7b73a6d4a2bc2a9f892732f49052e1d97052183..1f7f6aa8569364d4311173f2860461866ae210d3 100755 (executable)
@@ -17,6 +17,25 @@ notmuch show foo..
 exit_code=$?
 test_expect_equal 1 $exit_code
 
 exit_code=$?
 test_expect_equal 1 $exit_code
 
+test_begin_subtest "warning for --mbox --decrypt"
+notmuch show --format=mbox --decrypt=true '*' 1>/dev/null 2>OUTPUT
+echo $? >> OUTPUT
+cat <<EOF > EXPECTED
+Warning: mbox format does not support decryption (ignored)
+Warning: mbox format does not support signature verification (ignored)
+0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "warning for --mbox --verify"
+notmuch show --format=mbox --verify '*' 1>/dev/null 2>OUTPUT
+echo $? >> OUTPUT
+cat <<EOF > EXPECTED
+Warning: mbox format does not support signature verification (ignored)
+0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "notmuch show --sort=newest-first"
 notmuch show --entire-thread=true '*' > EXPECTED
 notmuch show --entire-thread=true --sort=newest-first '*' > OUTPUT
 test_begin_subtest "notmuch show --sort=newest-first"
 notmuch show --entire-thread=true '*' > EXPECTED
 notmuch show --entire-thread=true --sort=newest-first '*' > OUTPUT
@@ -35,7 +54,7 @@ notmuch show --entire-thread=true --sort=oldest-first $QUERY > OUTPUT
 test_expect_equal_file EXPECTED OUTPUT
 
 
 test_expect_equal_file EXPECTED OUTPUT
 
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
 
     test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
     test_query_syntax '(or "php" "wizard")' 'php or wizard'
 
     test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
     test_query_syntax '(or "php" "wizard")' 'php or wizard'
@@ -45,6 +64,12 @@ if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
 
 fi
 
 
 fi
 
+test_begin_subtest "quoting in From"
+test_subtest_known_broken
+add_message '[from]="=?UTF-8?Q?=3Cfoo=40example.org=3E=2C?= <bar@example.org>"'
+output=$(notmuch show id:${gen_msg_id}|grep From:)
+test_expect_equal "${output}" 'From: "<foo@example.org>," <bar@example.org>'
+
 add_email_corpus duplicate
 
 ID1=debian/2.6.1.dfsg-4-1-g87ea161@87ea161e851dfb1ea324af00e4ecfccc18875e15
 add_email_corpus duplicate
 
 ID1=debian/2.6.1.dfsg-4-1-g87ea161@87ea161e851dfb1ea324af00e4ecfccc18875e15
index 9d5a9e70a598089793d5d06b136dcbd0ae428ced..3048c7c4a5534c6a092836754ca35b33bc55fe26 100755 (executable)
@@ -3,6 +3,10 @@ test_description="database version and feature compatibility"
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 test_begin_subtest "future database versions abort open"
 ${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
 output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
 test_begin_subtest "future database versions abort open"
 ${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
 output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
index 511d56ca694e5c131d4d5815ad43d3e80ee5fbb1..6905193788a4b22419897119d2e1d14d50c405bc 100755 (executable)
@@ -3,6 +3,10 @@ test_description="API tests for notmuch_message_*"
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED}" ]; then
+    test_done
+fi
+
 add_email_corpus
 
 test_begin_subtest "building database"
 add_email_corpus
 
 test_begin_subtest "building database"
@@ -516,4 +520,31 @@ cat <<EOF > EXPECTED
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+TERMLIST_PATH=(${MAIL_DIR}/.notmuch/xapian/termlist.*)
+test_begin_subtest "remove message with corrupted db"
+backup_database
+cat c_head0 - c_tail <<'EOF' | test_private_C ${MAIL_DIR} ${TERMLIST_PATH}
+    {
+        notmuch_status_t status;
+
+        int fd = open(argv[2],O_WRONLY|O_TRUNC);
+        if (fd < 0) {
+            fprintf (stderr, "error opening %s\n", argv[1]);
+            exit (1);
+        }
+
+        stat = _notmuch_message_delete (message);
+        printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+    }
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+1
+== stderr ==
+A Xapian exception occurred at message.cc:XXX: EOF reading block YYY
+EOF
+sed 's/EOF reading block [0-9]*/EOF reading block YYY/' < OUTPUT > OUTPUT.clean
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
 test_done
 test_done
index a748005056d77c06ff511f8de2bf3d7333081364..bcc97dd9396305c7b984d156e575bdde87198804 100755 (executable)
@@ -95,7 +95,7 @@ subtotal=$(notmuch count lastmod:..$lastmod)
 result=$(($subtotal == $total-1))
 test_expect_equal 1 "$result"
 
 result=$(($subtotal == $total-1))
 test_expect_equal 1 "$result"
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
     test_begin_subtest 'exclude one message using negative lastmod (sexp)'
     total=$(notmuch count '*')
     notmuch tag +${RANDOM} id:4EFC743A.3060609@april.org
     test_begin_subtest 'exclude one message using negative lastmod (sexp)'
     total=$(notmuch count '*')
     notmuch tag +${RANDOM} id:4EFC743A.3060609@april.org
index 26a1f033085bf4470cac95e9a3dd24c380b53bd9..9b3648957b6a10eb1963815078f4bbf056ff2c80 100755 (executable)
@@ -440,6 +440,9 @@ cat <<'EOF' >EXPECTED
 10: 'USER_FULL_NAME'
 11: '8000'
 12: 'NULL'
 10: 'USER_FULL_NAME'
 11: '8000'
 12: 'NULL'
+13: ''
+14: ', '
+15: '| '
 == stderr ==
 EOF
 unset MAILDIR
 == stderr ==
 EOF
 unset MAILDIR
@@ -724,7 +727,10 @@ test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "list by keys (ndlc)"
 notmuch config set search.exclude_tags "foo;bar;fub"
 
 test_begin_subtest "list by keys (ndlc)"
 notmuch config set search.exclude_tags "foo;bar;fub"
+notmuch config set search.authors_matched_separator "| "
+notmuch config set search.authors_separator ", "
 notmuch config set new.ignore "sekrit_junk"
 notmuch config set new.ignore "sekrit_junk"
+notmuch config set index.as_text "text/"
 cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
 {
     notmuch_config_key_t key;
 cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
 {
     notmuch_config_key_t key;
@@ -751,6 +757,9 @@ cat <<'EOF' >EXPECTED
 10: 'Notmuch Test Suite'
 11: '8000'
 12: 'NULL'
 10: 'Notmuch Test Suite'
 11: '8000'
 12: 'NULL'
+13: 'text/'
+14: ', '
+15: '| '
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -785,6 +794,9 @@ cat <<'EOF' >EXPECTED
 10: 'USER_FULL_NAME'
 11: '8000'
 12: 'NULL'
 10: 'USER_FULL_NAME'
 11: '8000'
 12: 'NULL'
+13: ''
+14: ', '
+15: '| '
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT.clean
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT.clean
@@ -856,10 +868,13 @@ database.backup_dir MAIL_DIR/.notmuch/backups
 database.hook_dir MAIL_DIR/.notmuch/hooks
 database.mail_root MAIL_DIR
 database.path MAIL_DIR
 database.hook_dir MAIL_DIR/.notmuch/hooks
 database.mail_root MAIL_DIR
 database.path MAIL_DIR
+index.as_text text/
 key with spaces value, with, spaces!
 maildir.synchronize_flags true
 new.ignore sekrit_junk
 new.tags unread;inbox
 key with spaces value, with, spaces!
 maildir.synchronize_flags true
 new.ignore sekrit_junk
 new.tags unread;inbox
+search.authors_matched_separator | 
+search.authors_separator , 
 search.exclude_tags foo;bar;fub
 show.extra_headers (null)
 test.key1 testvalue1
 search.exclude_tags foo;bar;fub
 show.extra_headers (null)
 test.key1 testvalue1
index 92a70e3eb12123d4ba5a947f794727cbb7d66899..2334fcaf5266b1f297ba4495342f5c95f35aaaf8 100755 (executable)
@@ -21,6 +21,10 @@ test_description='thread breakage during reindexing'
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 message_a () {
     mkdir -p ${MAIL_DIR}/cur
     cat > ${MAIL_DIR}/cur/a <<EOF
 message_a () {
     mkdir -p ${MAIL_DIR}/cur
     cat > ${MAIL_DIR}/cur/a <<EOF
index 2685f3b5f5b04c62b274c46e51475f6526587a20..04a5d488418155f84fd3dfbd1e0afe77c23a1b09 100755 (executable)
@@ -12,7 +12,7 @@ void print_properties (notmuch_message_t *message, const char *prefix, notmuch_b
     notmuch_message_properties_t *list;
     for (list = notmuch_message_get_properties (message, prefix, exact);
          notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
     notmuch_message_properties_t *list;
     for (list = notmuch_message_get_properties (message, prefix, exact);
          notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
-       printf("%s\n", notmuch_message_properties_value(list));
+       printf("%s = %s\n", notmuch_message_properties_key(list), notmuch_message_properties_value(list));
     }
     notmuch_message_properties_destroy (list);
 }
     }
     notmuch_message_properties_destroy (list);
 }
@@ -89,17 +89,6 @@ testkey2 = NULL
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-test_begin_subtest "notmuch_message_remove_all_properties"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
-EXPECT0(notmuch_message_remove_all_properties (message, NULL));
-print_properties (message, "", FALSE);
-EOF
-cat <<'EOF' >EXPECTED
-== stdout ==
-== stderr ==
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
 test_begin_subtest "testing string map binary search (via message properties)"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
 test_begin_subtest "testing string map binary search (via message properties)"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
@@ -157,7 +146,28 @@ print_properties (message, "testkey1", TRUE);
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
-testvalue1
+testkey1 = testvalue1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_remove_all_properties"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_remove_all_properties (message, NULL));
+EXPECT0(notmuch_database_destroy(db));
+EXPECT0(notmuch_database_open_with_config (argv[1],
+                                           NOTMUCH_DATABASE_MODE_READ_WRITE,
+                                           "", NULL, &db, &msg));
+if (msg) fputs (msg, stderr);
+EXPECT0(notmuch_database_find_message(db, "4EFC743A.3060609@april.org", &message));
+if (message == NULL) {
+  fprintf (stderr, "unable to find message");
+  exit (1);
+}
+print_properties (message, "", FALSE);
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -171,10 +181,9 @@ print_properties (message, "testkey1", TRUE);
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
-alice
-bob
-testvalue1
-testvalue2
+testkey1 = alice
+testkey1 = bob
+testkey1 = testvalue2
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -186,27 +195,14 @@ EXPECT0(notmuch_message_add_property (message, "testkey3", "testvalue3"));
 EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3"));
 print_properties (message, "testkey", FALSE);
 EOF
 EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3"));
 print_properties (message, "testkey", FALSE);
 EOF
-# expected: 4 values for testkey1, 3 values for testkey3
-# they are not guaranteed to be sorted, so sort them, leaving the first
-# line '== stdout ==' and the end ('== stderr ==' and whatever error
-# may have been printed) alone
-mv OUTPUT unsorted_OUTPUT
-awk ' NR == 1 { print; next } \
-      NR < 6  { print | "sort"; next } \
-      NR == 6 { close("sort") } \
-      NR < 9  { print | "sort"; next } \
-      NR == 9 { close("sort") } \
-      { print }' unsorted_OUTPUT > OUTPUT
-rm unsorted_OUTPUT
 cat <<'EOF' >EXPECTED
 == stdout ==
 cat <<'EOF' >EXPECTED
 == stdout ==
-alice
-bob
-testvalue1
-testvalue2
-alice3
-bob3
-testvalue3
+testkey1 = alice
+testkey1 = bob
+testkey1 = testvalue2
+testkey3 = alice3
+testkey3 = bob3
+testkey3 = testvalue3
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -248,7 +244,7 @@ test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "dump message properties"
 cat <<EOF > PROPERTIES
 
 test_begin_subtest "dump message properties"
 cat <<EOF > PROPERTIES
-#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
+#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
 EOF
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 EXPECT0(notmuch_message_add_property (message, "fancy key with áccènts", "import value with ="));
 EOF
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 EXPECT0(notmuch_message_add_property (message, "fancy key with áccènts", "import value with ="));
@@ -259,7 +255,7 @@ test_expect_equal_file PROPERTIES OUTPUT
 test_begin_subtest "dump _only_ message properties"
 cat <<EOF > EXPECTED
 #notmuch-dump batch-tag:3 properties
 test_begin_subtest "dump _only_ message properties"
 cat <<EOF > EXPECTED
 #notmuch-dump batch-tag:3 properties
-#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
+#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
 EOF
 notmuch dump --include=properties > OUTPUT
 test_expect_equal_file EXPECTED OUTPUT
 EOF
 notmuch dump --include=properties > OUTPUT
 test_expect_equal_file EXPECTED OUTPUT
@@ -303,48 +299,32 @@ id:4EFC743A.3060609@april.org
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-test_begin_subtest "msg.get_property (python)"
+test_begin_subtest "msg.properties (python)"
 test_python <<'EOF'
 test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
-msg = db.find_message("4EFC743A.3060609@april.org")
-print("testkey1 = {0}".format(msg.get_property("testkey1")))
-print("testkey3 = {0}".format(msg.get_property("testkey3")))
-EOF
-cat <<'EOF' > EXPECTED
-testkey1 = alice
-testkey3 = alice3
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
-test_begin_subtest "msg.get_properties (python)"
-test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-msg = db.find_message("4EFC743A.3060609@april.org")
-for (key,val) in msg.get_properties("testkey1"):
+import notmuch2
+db = notmuch2.Database(mode=notmuch2.Database.MODE.READ_ONLY)
+msg = db.find("4EFC743A.3060609@april.org")
+for (key,val) in msg.properties.getall("testkey1"):
         print("{0} = {1}".format(key,val))
 EOF
 cat <<'EOF' > EXPECTED
 testkey1 = alice
 testkey1 = bob
         print("{0} = {1}".format(key,val))
 EOF
 cat <<'EOF' > EXPECTED
 testkey1 = alice
 testkey1 = bob
-testkey1 = testvalue1
 testkey1 = testvalue2
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 testkey1 = testvalue2
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-test_begin_subtest "msg.get_properties (python, prefix)"
+test_begin_subtest "msg.properties (python, prefix)"
 test_python <<'EOF'
 test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-msg = db.find_message("4EFC743A.3060609@april.org")
-for (key,val) in msg.get_properties("testkey"):
+import notmuch2
+db = notmuch2.Database(mode=notmuch2.Database.MODE.READ_ONLY)
+msg = db.find("4EFC743A.3060609@april.org")
+for (key,val) in msg.properties.getall("testkey"):
         print("{0} = {1}".format(key,val))
 EOF
 cat <<'EOF' > EXPECTED
 testkey1 = alice
 testkey1 = bob
         print("{0} = {1}".format(key,val))
 EOF
 cat <<'EOF' > EXPECTED
 testkey1 = alice
 testkey1 = bob
-testkey1 = testvalue1
 testkey1 = testvalue2
 testkey3 = alice3
 testkey3 = bob3
 testkey1 = testvalue2
 testkey3 = alice3
 testkey3 = bob3
@@ -352,14 +332,59 @@ testkey3 = testvalue3
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-test_begin_subtest "msg.get_properties (python, exact)"
+test_begin_subtest "msg.properties (python, exact)"
 test_python <<'EOF'
 test_python <<'EOF'
-import notmuch
-db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
-msg = db.find_message("4EFC743A.3060609@april.org")
-for (key,val) in msg.get_properties("testkey",True):
+import notmuch2
+db = notmuch2.Database(mode=notmuch2.Database.MODE.READ_ONLY)
+msg = db.find("4EFC743A.3060609@april.org")
+for (key,val) in msg.properties("testkey",True):
         print("{0} = {1}".format(key,val))
 EOF
 test_expect_equal_file /dev/null OUTPUT
 
         print("{0} = {1}".format(key,val))
 EOF
 test_expect_equal_file /dev/null OUTPUT
 
+test_begin_subtest "notmuch_message_remove_all_properties_with_prefix"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_remove_all_properties_with_prefix (message, "testkey3"));
+print_properties (message, "", FALSE);
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+fancy key with áccènts = import value with =
+testkey1 = alice
+testkey1 = bob
+testkey1 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "edit property on removed message without uncaught exception"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_database_remove_message (db, notmuch_message_get_filename (message)));
+stat = notmuch_message_remove_property (message, "example", "example");
+if (stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION)
+    fprintf (stderr, "unable to remove properties on message");
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+unable to remove properties on message
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+add_email_corpus
+
+test_begin_subtest "remove all properties on removed message without uncaught exception"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_database_remove_message (db, notmuch_message_get_filename (message)));
+stat = notmuch_message_remove_all_properties_with_prefix (message, "");
+if (stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION)
+    fprintf (stderr, "unable to remove properties on message");
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+unable to remove properties on message
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_done
 test_done
index c443f417c40824786b3fdd38bf7f6f5e2e509039..7d0da21a18bc0babde0107cfa05113f46929c209 100755 (executable)
@@ -71,4 +71,36 @@ Fcc: MAIL_DIR/sent
 <#secure method=pgpmime mode=sign>
 EOF
 test_expect_equal_file EXPECTED OUTPUT.clean
 <#secure method=pgpmime mode=sign>
 EOF
 test_expect_equal_file EXPECTED OUTPUT.clean
+
+add_email_corpus attachment
+test_begin_subtest "Saving a draft keeps hidden headers"
+test_emacs '(notmuch-mua-new-reply "id:874llc2bkp.fsf@curie.anarc.at")
+            (message-goto-subject)
+            (delete-region (line-beginning-position) (line-beginning-position 2))
+            (insert "Subject: draft-test-reply\n")
+           (test-output "DRAFT")
+           (notmuch-draft-postpone)
+           (notmuch-show "subject:draft-test-reply")
+           (notmuch-show-resume-message)
+           (test-output)'
+notmuch_dir_sanitize OUTPUT > OUTPUT.clean
+
+cat <<EOF > EXPECTED
+References: <87d10042pu.fsf@curie.anarc.at> <87woy8vx7i.fsf@tesseract.cs.unb.ca> <87a7v42bv9.fsf@curie.anarc.at> <874llc2bkp.fsf@curie.anarc.at>
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Antoine Beaupré <anarcat@orangeseeds.org>
+Subject: draft-test-reply
+In-Reply-To: <874llc2bkp.fsf@curie.anarc.at>
+Fcc: MAIL_DIR/sent
+--text follows this line--
+Antoine Beaupré <anarcat@orangeseeds.org> writes:
+
+> And obviously I forget the frigging attachment.
+>
+>
+> PS: don't we have a "you forgot to actually attach the damn file" plugin
+> when we detect the word "attachment" and there's no attach? :p
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+
 test_done
 test_done
index 347f84838fa2e7d7310488f460f4ec6344ed405a..af34ad7cb9e756636377604c3f01adff88d0bf6f 100755 (executable)
@@ -5,7 +5,7 @@ test_description='reindexing messages'
 add_email_corpus
 
 
 add_email_corpus
 
 
-if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
 
     count=$(notmuch count --lastmod '*' | cut -f 3)
     for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
 
     count=$(notmuch count --lastmod '*' | cut -f 3)
     for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
index 5129d84c544dc5f0b101fed30a51da0e5cd7c590..a2d8ec71ecd12c043217bc09b122d3a8a3e02dfc 100755 (executable)
@@ -3,6 +3,10 @@ test_description="message id parsing"
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
 
 . $(dirname "$0")/test-lib.sh || exit 1
 
+if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    test_done
+fi
+
 test_begin_subtest "good message ids"
 ${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
 <018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
 test_begin_subtest "good message ids"
 ${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
 <018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
diff --git a/test/T760-as-text.sh b/test/T760-as-text.sh
new file mode 100755 (executable)
index 0000000..744567f
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+test_description='index attachments as text'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus indexing
+test_begin_subtest "empty as_text; skip text/x-diff"
+messages=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain)
+count=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz)
+test_expect_equal "$messages,$count" "1,0"
+
+notmuch config set index.as_text "^text/"
+add_email_corpus indexing
+
+test_begin_subtest "as_index is text/; find text/x-diff"
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
+test_expect_equal_file_nonempty EXPECTED OUTPUT
+
+test_begin_subtest "reindex with empty as_text, skips text/x-diff"
+notmuch config set index.as_text
+notmuch reindex '*'
+messages=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain)
+count=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz)
+test_expect_equal "$messages,$count" "1,0"
+
+test_begin_subtest "reindex with empty as_text; skips application/pdf"
+notmuch config set index.as_text
+notmuch reindex '*'
+gmessages=$(notmuch count id:871qo9p4tf.fsf@tethera.net)
+count=$(notmuch count id:871qo9p4tf.fsf@tethera.net and body:not-really-PDF)
+test_expect_equal "$messages,$count" "1,0"
+
+test_begin_subtest "reindex with as_text as text/; finds text/x-diff"
+notmuch config set index.as_text "^text/"
+notmuch reindex '*'
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
+test_expect_equal_file_nonempty EXPECTED OUTPUT
+
+test_begin_subtest "reindex with as_text as text/; skips application/pdf"
+notmuch config set index.as_text "^text/"
+notmuch config set index.as_text
+notmuch reindex '*'
+messages=$(notmuch count id:871qo9p4tf.fsf@tethera.net)
+count=$(notmuch count id:871qo9p4tf.fsf@tethera.net and body:not-really-PDF)
+test_expect_equal "$messages,$count" "1,0"
+
+test_begin_subtest "as_text has multiple regexes"
+notmuch config set index.as_text "blahblah;^text/"
+notmuch reindex '*'
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
+test_expect_equal_file_nonempty EXPECTED OUTPUT
+
+test_begin_subtest "as_text is non-anchored regex"
+notmuch config set index.as_text "e.t/"
+notmuch reindex '*'
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
+notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
+test_expect_equal_file_nonempty EXPECTED OUTPUT
+
+test_begin_subtest "as_text is 'application/pdf'"
+notmuch config set index.as_text "^application/pdf$"
+notmuch reindex '*'
+notmuch search id:871qo9p4tf.fsf@tethera.net > EXPECTED
+notmuch search id:871qo9p4tf.fsf@tethera.net and '"not really PDF"' > OUTPUT
+test_expect_equal_file_nonempty EXPECTED OUTPUT
+
+test_begin_subtest "as_text is bad regex"
+notmuch config set index.as_text '['
+notmuch reindex '*' >& OUTPUT
+cat<<EOF > EXPECTED
+Error in index.as_text: Invalid regular expression: [
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T770-emacs-subject-warnings.sh b/test/T770-emacs-subject-warnings.sh
new file mode 100755 (executable)
index 0000000..f9d2714
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+test_description="emacs subject warnings"
+. $(dirname "$0")/test-lib.sh || exit 1
+. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
+
+test_require_emacs
+
+test_begin_subtest "notmuch-test-subject-warning part 1"
+test_emacs_expect_t '(notmuch-test-subject-warning-1)'
+
+test_done
index 8607732e176b9978bd72622a6e5ddf8a255e6710..9ce6baa7c80b34e767bd8900f2f74c8498676818 100755 (executable)
@@ -2,14 +2,19 @@
 test_description='run code with ASAN enabled against the library'
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description='run code with ASAN enabled against the library'
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ $NOTMUCH_HAVE_ASAN -ne 1 ]; then
+if [ "${NOTMUCH_HAVE_ASAN-0}" != "1" ]; then
     printf "Skipping due to missing ASAN support\n"
     test_done
 fi
 
     printf "Skipping due to missing ASAN support\n"
     test_done
 fi
 
+if [ -n "${LD_PRELOAD-}" ]; then
+    printf "Skipping due to ASAN LD_PRELOAD restrictions\n"
+    test_done
+fi
+
 add_email_corpus
 
 add_email_corpus
 
-TEST_CFLAGS="-fsanitize=address"
+TEST_CFLAGS="${TEST_CFLAGS:-} -fsanitize=address"
 
 test_begin_subtest "open and destroy"
 test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} <<EOF
 
 test_begin_subtest "open and destroy"
 test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} <<EOF
diff --git a/test/T810-tsan.sh b/test/T810-tsan.sh
new file mode 100755 (executable)
index 0000000..1829a73
--- /dev/null
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+
+test_directory=$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)
+
+test_description='run code with TSan enabled against the library'
+# Note it is hard to ensure race conditions are deterministic so this
+# only provides best effort detection. Compile Notmuch with
+#  make CFLAGS=-fsanitize=thread LDFLAGS=-fsanitize=thread
+
+. "$test_directory"/test-lib.sh || exit 1
+
+if [ "${NOTMUCH_HAVE_TSAN-0}" != "1" ]; then
+    printf "Skipping due to missing TSan support\n"
+    test_done
+fi
+
+export TSAN_OPTIONS="suppressions=$test_directory/T810-tsan.suppressions"
+TEST_CFLAGS="${TEST_CFLAGS:-} -fsanitize=thread"
+
+cp -r ${MAIL_DIR} ${MAIL_DIR}-2
+
+test_begin_subtest "create"
+test_C ${MAIL_DIR} ${MAIL_DIR}-2 <<EOF
+#include <notmuch-test.h>
+#include <pthread.h>
+
+void *thread (void *arg) {
+  char *mail_dir = arg;
+  /*
+   * Calls into notmuch_query_search_messages which was using the thread-unsafe
+   * Xapian::Query::MatchAll.
+   */
+  EXPECT0(notmuch_database_create (mail_dir, NULL));
+  return NULL;
+}
+
+int main (int argc, char **argv) {
+  pthread_t t1, t2;
+  EXPECT0(pthread_create (&t1, NULL, thread, argv[1]));
+  EXPECT0(pthread_create (&t2, NULL, thread, argv[2]));
+  EXPECT0(pthread_join (t1, NULL));
+  EXPECT0(pthread_join (t2, NULL));
+  return 0;
+}
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+add_email_corpus
+rm -r ${MAIL_DIR}-2
+cp -r ${MAIL_DIR} ${MAIL_DIR}-2
+
+test_begin_subtest "query"
+test_C ${MAIL_DIR} ${MAIL_DIR}-2 <<EOF
+#include <notmuch-test.h>
+#include <pthread.h>
+
+void *thread (void *arg) {
+  char *mail_dir = arg;
+  notmuch_database_t *db;
+  /*
+   * 'from' is NOTMUCH_FIELD_PROBABILISTIC | NOTMUCH_FIELD_PROCESSOR and an
+   * empty string gets us to RegexpFieldProcessor::operator which was using
+   * the tread-unsafe Xapian::Query::MatchAll.
+   */
+  EXPECT0(notmuch_database_open_with_config (mail_dir,
+                                             NOTMUCH_DATABASE_MODE_READ_ONLY,
+                                             NULL, NULL, &db, NULL));
+  notmuch_query_t *query = notmuch_query_create (db, "from:\"\"");
+  notmuch_messages_t *messages;
+  EXPECT0(notmuch_query_search_messages (query, &messages));
+  return NULL;
+}
+
+int main (int argc, char **argv) {
+  pthread_t t1, t2;
+  EXPECT0(pthread_create (&t1, NULL, thread, argv[1]));
+  EXPECT0(pthread_create (&t2, NULL, thread, argv[2]));
+  EXPECT0(pthread_join (t1, NULL));
+  EXPECT0(pthread_join (t2, NULL));
+  return 0;
+}
+EOF
+cat <<EOF > EXPECTED
+== stdout ==
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+    test_begin_subtest "sexp query"
+    test_C ${MAIL_DIR} ${MAIL_DIR}-2 <<EOF
+#include <notmuch-test.h>
+#include <pthread.h>
+
+void *thread (void *arg) {
+  char *mail_dir = arg;
+  notmuch_database_t *db;
+  /*
+   * Query generation from s-expression used the tread-unsafe
+   * Xapian::Query::MatchAll.
+   */
+  EXPECT0(notmuch_database_open_with_config (mail_dir,
+                                             NOTMUCH_DATABASE_MODE_READ_ONLY,
+                                             NULL, NULL, &db, NULL));
+  notmuch_query_t *query;
+  EXPECT0(notmuch_query_create_with_syntax (db, "(from *)", NOTMUCH_QUERY_SYNTAX_SEXP, &query));
+  notmuch_messages_t *messages;
+  EXPECT0(notmuch_query_search_messages (query, &messages));
+  return NULL;
+}
+
+int main (int argc, char **argv) {
+  pthread_t t1, t2;
+  EXPECT0(pthread_create (&t1, NULL, thread, argv[1]));
+  EXPECT0(pthread_create (&t2, NULL, thread, argv[2]));
+  EXPECT0(pthread_join (t1, NULL));
+  EXPECT0(pthread_join (t2, NULL));
+  return 0;
+}
+EOF
+    cat <<EOF > EXPECTED
+== stdout ==
+== stderr ==
+EOF
+    test_expect_equal_file EXPECTED OUTPUT
+fi
+
+test_done
diff --git a/test/T810-tsan.suppressions b/test/T810-tsan.suppressions
new file mode 100644 (file)
index 0000000..80dc062
--- /dev/null
@@ -0,0 +1,3 @@
+# It's unclear how TSan-friendly GLib is:
+# https://gitlab.gnome.org/GNOME/glib/-/issues/1672
+called_from_lib:libglib*.so
index 55cec78a16391b3f01e4139d5086729ca7f94bfb..2591521b10ec3a9f495da85603945d1976cee4ca 100755 (executable)
@@ -2,7 +2,7 @@
 test_description='"notmuch git" to save and restore tags'
 . $(dirname "$0")/test-lib.sh || exit 1
 
 test_description='"notmuch git" to save and restore tags'
 . $(dirname "$0")/test-lib.sh || exit 1
 
-if [ $NOTMUCH_HAVE_SFSEXP -ne 1 ]; then
+if [ "${NOTMUCH_HAVE_SFSEXP-0}" != "1" ]; then
     printf "Skipping due to missing sfsexp library\n"
     test_done
 fi
     printf "Skipping due to missing sfsexp library\n"
     test_done
 fi
@@ -213,6 +213,51 @@ cat <<EOF > EXPECTED
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
+test_begin_subtest "reset"
+notmuch git -C reset.git -p '' clone remote.git
+notmuch git -C reset.git checkout --force
+notmuch tag +test4 id:20091117190054.GU3165@dottiness.seas.harvard.edu
+notmuch git -C remote.git commit --force
+notmuch tag -test4 id:20091117190054.GU3165@dottiness.seas.harvard.edu
+notmuch git -C reset.git fetch
+notmuch git -C reset.git reset
+notmuch git -C reset.git checkout --force
+notmuch dump id:20091117190054.GU3165@dottiness.seas.harvard.edu | grep -v '^#' > OUTPUT
+cat <<EOF > EXPECTED
++inbox +signed +test2 +test3 +test4 +unread -- id:20091117190054.GU3165@dottiness.seas.harvard.edu
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reset (require force for large change)"
+notmuch git -C reset2.git -p '' clone remote.git
+notmuch git -C reset2.git checkout --force
+notmuch tag +test5 '*'
+notmuch git -C remote.git commit --force
+notmuch tag -test5 '*'
+notmuch git -C reset2.git fetch
+test_expect_code 1 "notmuch git -C reset2.git -l debug reset"
+
+test_begin_subtest "reset (don't require force for large change to one message)"
+notmuch git -C reset3.git -p '' clone remote.git
+notmuch git -C reset3.git checkout --force
+notmuch dump id:20091117190054.GU3165@dottiness.seas.harvard.edu > BEFORE
+for tag in $(seq 1 100); do
+    notmuch tag +$tag id:20091117190054.GU3165@dottiness.seas.harvard.edu
+done
+notmuch git -C remote.git commit --force
+notmuch restore < BEFORE
+notmuch git -C reset3.git fetch
+test_expect_code 0 "notmuch git -C reset3.git -l debug reset"
+
+test_begin_subtest "reset --force"
+notmuch git -C reset4.git -p '' clone remote.git
+notmuch git -C reset4.git checkout --force
+notmuch tag +test6 '*'
+notmuch git -C remote.git commit --force
+notmuch tag -test6 '*'
+notmuch git -C reset4.git fetch
+test_expect_code 0 "notmuch git -C reset4.git -l debug reset --force"
+
 test_begin_subtest "environment passed through when run as 'notmuch git'"
 env NOTMUCH_GIT_DIR=foo NOTMUCH_GIT_PREFIX=bar NOTMUCH_PROFILE=default notmuch git -C tags.git -p '' -ldebug status |& \
     grep '^env ' | notmuch_dir_sanitize > OUTPUT
 test_begin_subtest "environment passed through when run as 'notmuch git'"
 env NOTMUCH_GIT_DIR=foo NOTMUCH_GIT_PREFIX=bar NOTMUCH_PROFILE=default notmuch git -C tags.git -p '' -ldebug status |& \
     grep '^env ' | notmuch_dir_sanitize > OUTPUT
@@ -233,6 +278,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "invoke as nmbug sets defaults"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "invoke as nmbug sets defaults"
+test_subtest_broken_for_installed
 "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^\(prefix\|repository\)' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 prefix = notmuch::
 "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^\(prefix\|repository\)' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 prefix = notmuch::
@@ -241,6 +287,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_DIR works when invoked as nmbug"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_DIR works when invoked as nmbug"
+test_subtest_broken_for_installed
 NOTMUCH_GIT_DIR=`pwd`/foo "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 repository = CWD/foo
 NOTMUCH_GIT_DIR=`pwd`/foo "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 repository = CWD/foo
@@ -256,6 +303,7 @@ test_expect_equal_file EXPECTED OUTPUT
 
 
 test_begin_subtest "env variable NOTMUCH_GIT_DIR overrides config when invoked as 'nmbug'"
 
 
 test_begin_subtest "env variable NOTMUCH_GIT_DIR overrides config when invoked as 'nmbug'"
+test_subtest_broken_for_installed
 notmuch config set git.path `pwd`/bar
 NOTMUCH_GIT_DIR=`pwd`/remote.git  "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
 notmuch config set git.path
 notmuch config set git.path `pwd`/bar
 NOTMUCH_GIT_DIR=`pwd`/remote.git  "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
 notmuch config set git.path
@@ -274,6 +322,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as 'nmbug'"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as 'nmbug'"
+test_subtest_broken_for_installed
 NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 prefix = env::
 NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 prefix = env::
@@ -281,6 +330,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as nmbug"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as nmbug"
+test_subtest_broken_for_installed
 NOTMUCH_GIT_PREFIX=foo:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 prefix = foo::
 NOTMUCH_GIT_PREFIX=foo:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
 cat <<EOF > EXPECTED
 prefix = foo::
@@ -288,6 +338,7 @@ EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_PREFIX overrides config when invoked as 'nmbug'"
 test_expect_equal_file EXPECTED OUTPUT
 
 test_begin_subtest "env variable NOTMUCH_GIT_PREFIX overrides config when invoked as 'nmbug'"
+test_subtest_broken_for_installed
 notmuch config set git.tag_prefix config::
 NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
 notmuch config set git.path
 notmuch config set git.tag_prefix config::
 NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
 notmuch config set git.path
@@ -305,7 +356,6 @@ prefix = env::
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
 EOF
 test_expect_equal_file EXPECTED OUTPUT
 
-
 test_begin_subtest "init, xdg default location"
 repo=home/.local/share/notmuch/default/git
 notmuch git -ldebug init |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
 test_begin_subtest "init, xdg default location"
 repo=home/.local/share/notmuch/default/git
 notmuch git -ldebug init |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
diff --git a/test/corpora/attachment/text-calendar.eml b/test/corpora/attachment/text-calendar.eml
new file mode 100644 (file)
index 0000000..8e064f9
--- /dev/null
@@ -0,0 +1,55 @@
+From: David Bremner <bremner@example.com>
+To: "david@tethera.net" <david@tethera.net>
+Subject: test
+Date: Thu Jan 01 00:00:10 +0000
+Message-ID:
+ <YT3PR01MB10572EFC9F7C81F9446214768CEF72@YT3PR01MB10572.CANPRD01.PROD.OUTLOOK.COM>
+Accept-Language: en-CA, en-US
+Content-Language: en-CA
+Content-Type: multipart/alternative;
+       boundary="_000_YT3PR01MB10572EFC9F7C81F9446214768CEF72YT3PR01MB10572CA_"
+MIME-Version: 1.0
+
+--_000_YT3PR01MB10572EFC9F7C81F9446214768CEF72YT3PR01MB10572CA_
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+This meeting will could have been an email
+
+
+--_000_YT3PR01MB10572EFC9F7C81F9446214768CEF72YT3PR01MB10572CA_
+Content-Type: text/calendar; charset="utf-8"; method=REQUEST
+Content-Transfer-Encoding: base64
+
+QkVHSU46VkNBTEVOREFSDQpNRVRIT0Q6UkVRVUVTVA0KUFJPRElEOk1pY3Jvc29mdCBFeGNoYW5n
+ZSBTZXJ2ZXIgMjAxMA0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBdGxhbnRp
+YyBTdGFuZGFyZCBUaW1lDQpCRUdJTjpTVEFOREFSRA0KRFRTVEFSVDoxNjAxMDEwMVQwMjAwMDAN
+ClRaT0ZGU0VURlJPTTotMDMwMA0KVFpPRkZTRVRUTzotMDQwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7
+SU5URVJWQUw9MTtCWURBWT0xU1U7QllNT05USD0xMQ0KRU5EOlNUQU5EQVJEDQpCRUdJTjpEQVlM
+SUdIVA0KRFRTVEFSVDoxNjAxMDEwMVQwMjAwMDANClRaT0ZGU0VURlJPTTotMDQwMA0KVFpPRkZT
+RVRUTzotMDMwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9MTtCWURBWT0yU1U7QllNT05U
+SD0zDQpFTkQ6REFZTElHSFQNCkVORDpWVElNRVpPTkUNCkJFR0lOOlZFVkVOVA0KT1JHQU5JWkVS
+O0NOPURhdmlkIEJyZW1uZXI6bWFpbHRvOmJyZW1uZXJAZXhhbXBsZS5jb20NCkFUVEVOREVFO1JP
+TEU9UkVRLVBBUlRJQ0lQQU5UO1BBUlRTVEFUPU5FRURTLUFDVElPTjtSU1ZQPVRSVUU7Q049ZGF2
+aWRAdGV0aA0KIGVyYS5uZXQ6bWFpbHRvOmRhdmlkQHRldGhlcmEubmV0DQpERVNDUklQVElPTjtM
+QU5HVUFHRT1lbi1DQTpUaGlzIG1lZXRpbmcgd2lsbCBjb3VsZCBoYXZlIGJlZW4gYW4gZW1haWxc
+blxuDQpSUlVMRTpGUkVRPURBSUxZO1VOVElMPTIwMjQwODI3VDE3MDAwMFo7SU5URVJWQUw9MQ0K
+VUlEOjA0MDAwMDAwODIwMEUwMDA3NEM1QjcxMDFBODJFMDA4MDAwMDAwMDAwRUE4NDFFNzk5QUZE
+QTAxMDAwMDAwMDAwMDAwMDAwDQogMDEwMDAwMDAwNjkyRkRFNzQzMzNGQ0I0N0JFNEY0REU5MDk0
+OThDQkUNClNVTU1BUlk7TEFOR1VBR0U9ZW4tQ0E6dGVzdCBbSW4tcGVyc29uXQ0KRFRTVEFSVDtU
+WklEPUF0bGFudGljIFN0YW5kYXJkIFRpbWU6MjAyNDA1MjdUMTQwMDAwDQpEVEVORDtUWklEPUF0
+bGFudGljIFN0YW5kYXJkIFRpbWU6MjAyNDA1MjdUMTQzMDAwDQpDTEFTUzpQVUJMSUMNClBSSU9S
+SVRZOjUNCkRUU1RBTVA6MjAyNDA1MjZUMTgyNDEwWg0KVFJBTlNQOk9QQVFVRQ0KU1RBVFVTOkNP
+TkZJUk1FRA0KU0VRVUVOQ0U6MA0KTE9DQVRJT047TEFOR1VBR0U9ZW4tQ0E6DQpYLU1JQ1JPU09G
+VC1DRE8tQVBQVC1TRVFVRU5DRTowDQpYLU1JQ1JPU09GVC1DRE8tT1dORVJBUFBUSUQ6MjEyMjcw
+MDA0Ng0KWC1NSUNST1NPRlQtQ0RPLUJVU1lTVEFUVVM6VEVOVEFUSVZFDQpYLU1JQ1JPU09GVC1D
+RE8tSU5URU5ERURTVEFUVVM6QlVTWQ0KWC1NSUNST1NPRlQtQ0RPLUFMTERBWUVWRU5UOkZBTFNF
+DQpYLU1JQ1JPU09GVC1DRE8tSU1QT1JUQU5DRToxDQpYLU1JQ1JPU09GVC1DRE8tSU5TVFRZUEU6
+MQ0KWC1NSUNST1NPRlQtRE9OT1RGT1JXQVJETUVFVElORzpGQUxTRQ0KWC1NSUNST1NPRlQtRElT
+QUxMT1ctQ09VTlRFUjpGQUxTRQ0KWC1NSUNST1NPRlQtUkVRVUVTVEVEQVRURU5EQU5DRU1PREU6
+SU5QRVJTT05SRVFVSVJFRA0KWC1NSUNST1NPRlQtSVNSRVNQT05TRVJFUVVFU1RFRDpUUlVFDQpY
+LU1JQ1JPU09GVC1MT0NBVElPTlM6W10NCkJFR0lOOlZBTEFSTQ0KREVTQ1JJUFRJT046UkVNSU5E
+RVINClRSSUdHRVI7UkVMQVRFRD1TVEFSVDotUFQxNU0NCkFDVElPTjpESVNQTEFZDQpFTkQ6VkFM
+QVJNDQpFTkQ6VkVWRU5UDQpFTkQ6VkNBTEVOREFSDQo=
+
+--_000_YT3PR01MB10572EFC9F7C81F9446214768CEF72YT3PR01MB10572CA_--
diff --git a/test/corpora/indexing/fake-pdf:2,S b/test/corpora/indexing/fake-pdf:2,S
new file mode 100644 (file)
index 0000000..60a7a47
--- /dev/null
@@ -0,0 +1,11 @@
+From: David Bremner <david@tethera.net>
+To: example@example.com
+Subject: attachment content type
+Date: Thu, 05 Jan 2023 08:02:36 -0400
+Message-ID: <871qo9p4tf.fsf@tethera.net>
+MIME-Version: 1.0
+Content-Type: application/pdf
+Content-Disposition: attachment; filename=fake.pdf
+Content-Transfer-Encoding: base64
+
+dGhpcyBpcyBub3QgcmVhbGx5IFBERgo=
\ No newline at end of file
diff --git a/test/emacs-exclude.expected-output/notmuch-search-tag-inbox-with-excluded b/test/emacs-exclude.expected-output/notmuch-search-tag-inbox-with-excluded
new file mode 100644 (file)
index 0000000..ce1d711
--- /dev/null
@@ -0,0 +1,25 @@
+  2009-11-17 [5/5]   Mikhail Gusarov, Carl Worth, Keith Packard  [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17 [7/7]   Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth  [notmuch] Working with Maildir storage? (inbox signed unread)
+  2009-11-17 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] preliminary FreeBSD support (attachment inbox unread)
+  2009-11-17 [1/1]   Mikhail Gusarov      [notmuch] [PATCH] Handle rename of message file (inbox unread)
+  2009-11-17 [2/2]   Keith Packard, Carl Worth    [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17 [2/2]   Jan Janak, Carl Worth        [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-17 [3/3]   Jan Janak, Carl Worth        [notmuch] What a great idea! (inbox unread)
+  2009-11-17 [3/3]   Israel Herraiz, Keith Packard, Carl Worth   [notmuch] New to the list (inbox unread)
+  2009-11-17 [3/3]   Adrian Perez de Castro, Keith Packard, Carl Worth  [notmuch] Introducing myself (inbox signed unread)
+  2009-11-17 [3/3]   Aron Griffis, Keith Packard, Carl Worth     [notmuch] archive (inbox unread)
+  2009-11-17 [2/2]   Ingmar Vanhassel, Carl Worth  [notmuch] [PATCH] Typsos (inbox unread)
+  2009-11-18 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18 [2/2]   Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (deleted inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH 2/2] Read mail directory in inode number order (deleted inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH] count_files: sort directory in inode order before statting (deleted inbox unread)
+  2009-11-18 [4/4]   Jjgod Jiang, Alexander Botero-Lowry      [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+  2009-11-18 [1/1]   Jan Janak            [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18 [1/1]   Rolland Santimano    [notmuch] Link to mailing list archives ? (inbox unread)
+  2009-11-18 [1/1]   Alexander Botero-Lowry  [notmuch] request for pull (inbox unread)
+  2009-11-18 [2/2]   Keith Packard, Alexander Botero-Lowry    [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18 [1/1]   Chris Wilson         [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (deleted inbox unread)
+  2010-12-16 [1/1]   Olivier Berger       Essai accentué (inbox unread)
+  2010-12-29 [1/1]   François Boulogne    [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+End of search results.
diff --git a/test/emacs-exclude.expected-output/notmuch-search-tag-inbox-without-excluded b/test/emacs-exclude.expected-output/notmuch-search-tag-inbox-without-excluded
new file mode 100644 (file)
index 0000000..8a87432
--- /dev/null
@@ -0,0 +1,21 @@
+  2009-11-17 [5/5]   Mikhail Gusarov, Carl Worth, Keith Packard  [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17 [7/7]   Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth  [notmuch] Working with Maildir storage? (inbox signed unread)
+  2009-11-17 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] preliminary FreeBSD support (attachment inbox unread)
+  2009-11-17 [1/1]   Mikhail Gusarov      [notmuch] [PATCH] Handle rename of message file (inbox unread)
+  2009-11-17 [2/2]   Keith Packard, Carl Worth    [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17 [2/2]   Jan Janak, Carl Worth        [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-17 [3/3]   Jan Janak, Carl Worth        [notmuch] What a great idea! (inbox unread)
+  2009-11-17 [3/3]   Israel Herraiz, Keith Packard, Carl Worth   [notmuch] New to the list (inbox unread)
+  2009-11-17 [3/3]   Adrian Perez de Castro, Keith Packard, Carl Worth  [notmuch] Introducing myself (inbox signed unread)
+  2009-11-17 [3/3]   Aron Griffis, Keith Packard, Carl Worth     [notmuch] archive (inbox unread)
+  2009-11-17 [2/2]   Ingmar Vanhassel, Carl Worth  [notmuch] [PATCH] Typsos (inbox unread)
+  2009-11-18 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18 [2/2]   Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+  2009-11-18 [4/4]   Jjgod Jiang, Alexander Botero-Lowry      [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+  2009-11-18 [1/1]   Jan Janak            [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18 [1/1]   Rolland Santimano    [notmuch] Link to mailing list archives ? (inbox unread)
+  2009-11-18 [1/1]   Alexander Botero-Lowry  [notmuch] request for pull (inbox unread)
+  2009-11-18 [2/2]   Keith Packard, Alexander Botero-Lowry    [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2010-12-16 [1/1]   Olivier Berger       Essai accentué (inbox unread)
+  2010-12-29 [1/1]   François Boulogne    [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+End of search results.
diff --git a/test/emacs-exclude.expected-output/notmuch-tree-tag-inbox-with-excluded b/test/emacs-exclude.expected-output/notmuch-tree-tag-inbox-with-excluded
new file mode 100644 (file)
index 0000000..0f640b3
--- /dev/null
@@ -0,0 +1,53 @@
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (deleted inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (deleted inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (deleted inbox unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (inbox unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (deleted inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+End of search results.
diff --git a/test/emacs-exclude.expected-output/notmuch-tree-tag-inbox-without-excluded b/test/emacs-exclude.expected-output/notmuch-tree-tag-inbox-without-excluded
new file mode 100644 (file)
index 0000000..34abae8
--- /dev/null
@@ -0,0 +1,49 @@
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+End of search results.
diff --git a/test/emacs-exclude.expected-output/notmuch-unthreaded-tag-inbox-with-excluded b/test/emacs-exclude.expected-output/notmuch-unthreaded-tag-inbox-with-excluded
new file mode 100644 (file)
index 0000000..ea8bf3d
--- /dev/null
@@ -0,0 +1,53 @@
+  2009-11-17  Mikhail Gusarov      [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth           [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-17  Alex Botero-Lowry    [notmuch] preliminary FreeBSD support                  (attachment inbox unread)
+  2009-11-17  Carl Worth           [notmuch] preliminary FreeBSD support                  (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] Working with Maildir storage?                (inbox unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] [PATCH] Handle rename of message file        (inbox unread)
+  2009-11-17  Keith Packard        [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard        [notmuch] Working with Maildir storage?                (inbox unread)
+  2009-11-17  Keith Packard        [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Jan Janak            [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-17  Jan Janak            [notmuch] What a great idea!                           (inbox unread)
+  2009-11-17  Jan Janak            [notmuch] What a great idea!                           (inbox unread)
+  2009-11-17  Israel Herraiz       [notmuch] New to the list                              (inbox unread)
+  2009-11-17  Adrian Perez de Cast [notmuch] Introducing myself                           (inbox signed unread)
+  2009-11-17  Aron Griffis         [notmuch] archive                                      (inbox unread)
+  2009-11-17  Ingmar Vanhassel     [notmuch] [PATCH] Typsos                               (inbox unread)
+  2009-11-18  Alex Botero-Lowry    [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Lars Kellogg-Stedman [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr?            (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr?            (attachment inbox signed unread)
+  2009-11-18  Stewart Smith        [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (deleted inbox unread)
+  2009-11-18  Stewart Smith        [notmuch] [PATCH 2/2] Read mail directory in inode number order (deleted inbox unread)
+  2009-11-18  Keith Packard        [notmuch] New to the list                              (inbox unread)
+  2009-11-18  Keith Packard        [notmuch] Introducing myself                           (inbox unread)
+  2009-11-18  Keith Packard        [notmuch] archive                                      (inbox unread)
+  2009-11-18  Stewart Smith        [notmuch] [PATCH] count_files: sort directory in inode order before statting (deleted inbox unread)
+  2009-11-18  Jjgod Jiang          [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Jan Janak            [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Rolland Santimano    [notmuch] Link to mailing list archives ?              (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Jjgod Jiang          [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] request for pull                             (inbox unread)
+  2009-11-18  Keith Packard        [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] Working with Maildir storage?                (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] archive                                      (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] What a great idea!                           (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] New to the list                              (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] Introducing myself                           (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Typsos                               (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-18  Chris Wilson         [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (deleted inbox unread)
+  2010-12-16  Olivier Berger       Essai accentué                                         (inbox unread)
+  2010-12-29  François Boulogne    [aur-general] Guidelines: cp, mkdir vs install         (inbox unread)
+End of search results.
diff --git a/test/emacs-exclude.expected-output/notmuch-unthreaded-tag-inbox-without-excluded b/test/emacs-exclude.expected-output/notmuch-unthreaded-tag-inbox-without-excluded
new file mode 100644 (file)
index 0000000..fabb43e
--- /dev/null
@@ -0,0 +1,49 @@
+  2009-11-17  Mikhail Gusarov      [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth           [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-17  Alex Botero-Lowry    [notmuch] preliminary FreeBSD support                  (attachment inbox unread)
+  2009-11-17  Carl Worth           [notmuch] preliminary FreeBSD support                  (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] Working with Maildir storage?                (inbox unread)
+  2009-11-17  Mikhail Gusarov      [notmuch] [PATCH] Handle rename of message file        (inbox unread)
+  2009-11-17  Keith Packard        [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard        [notmuch] Working with Maildir storage?                (inbox unread)
+  2009-11-17  Keith Packard        [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Jan Janak            [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-17  Jan Janak            [notmuch] What a great idea!                           (inbox unread)
+  2009-11-17  Jan Janak            [notmuch] What a great idea!                           (inbox unread)
+  2009-11-17  Israel Herraiz       [notmuch] New to the list                              (inbox unread)
+  2009-11-17  Adrian Perez de Cast [notmuch] Introducing myself                           (inbox signed unread)
+  2009-11-17  Aron Griffis         [notmuch] archive                                      (inbox unread)
+  2009-11-17  Ingmar Vanhassel     [notmuch] [PATCH] Typsos                               (inbox unread)
+  2009-11-18  Alex Botero-Lowry    [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Lars Kellogg-Stedman [notmuch] Working with Maildir storage?                (inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr?            (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr?            (attachment inbox signed unread)
+  2009-11-18  Keith Packard        [notmuch] New to the list                              (inbox unread)
+  2009-11-18  Keith Packard        [notmuch] Introducing myself                           (inbox unread)
+  2009-11-18  Keith Packard        [notmuch] archive                                      (inbox unread)
+  2009-11-18  Jjgod Jiang          [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Jan Janak            [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Rolland Santimano    [notmuch] Link to mailing list archives ?              (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Jjgod Jiang          [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] Mac OS X/Darwin compatibility issues         (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] request for pull                             (inbox unread)
+  2009-11-18  Keith Packard        [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] Working with Maildir storage?                (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] archive                                      (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] What a great idea!                           (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] New to the list                              (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] Introducing myself                           (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Typsos                               (inbox unread)
+  2009-11-18  Carl Worth           [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2010-12-16  Olivier Berger       Essai accentué                                         (inbox unread)
+  2010-12-29  François Boulogne    [aur-general] Guidelines: cp, mkdir vs install         (inbox unread)
+End of search results.
diff --git a/test/emacs-subject-warnings.el b/test/emacs-subject-warnings.el
new file mode 100644 (file)
index 0000000..1e54742
--- /dev/null
@@ -0,0 +1,48 @@
+(require 'cl-lib)
+(require 'notmuch-mua)
+
+(defun subject-check-test (&optional fn)
+  "Test `notmuch-mua-subject-check'.
+Optionally, evaluate FN before doing the test.
+
+Return t if the message would be sent, and nil otherwise."
+  (notmuch-mua-mail)
+  (message-goto-subject)
+  (when fn
+    (funcall fn))
+  (prog1
+      (condition-case nil
+         ;; Force `y-or-n-p' to always return `nil', as if the user
+         ;; pressed "n".
+         (cl-letf (((symbol-function 'y-or-n-p)
+                    (lambda (&rest args) nil)))
+           (notmuch-mua-subject-check)
+           t)
+       ('error nil))
+    (set-buffer-modified-p nil)
+    (kill-buffer (current-buffer))))
+
+(defvar subject-check-tests
+  '(;; These are okay.
+    (t . (lambda () (insert "something")))
+    ;; These should not be okay.
+    (nil)
+    (nil . (lambda () (insert "         ")))
+    (nil . (lambda () (insert "                        ")))
+    (nil . (lambda () (insert " ")))  ; NON-BREAKING SPACE
+    ))
+
+(defun notmuch-test-subject-warning-1 ()
+  (let (output expected)
+    (dolist (test subject-check-tests)
+      (let* ((expect (car test))
+            (body (cdr test))
+            (result (subject-check-test body)))
+       (push expect expected)
+       (push (if (eq result expect)
+                 result
+               ;; In the case of a failure, include the test
+               ;; details to make it simpler to debug.
+               (format "%S <-- %S" result body))
+             output)))
+    (notmuch-test-expect-equal output expected)))
diff --git a/test/emacs-tree.expected-output/inbox-outline b/test/emacs-tree.expected-output/inbox-outline
new file mode 100644 (file)
index 0000000..4acb62a
--- /dev/null
@@ -0,0 +1,25 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+End of search results.
index 2285d10ec944f18d52e4d8012d1492263110075b..3da7628d95aafef1a00f2f53185ea8b2a562f3b3 100644 (file)
@@ -1,5 +1,5 @@
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
index f28d4856511d33e96d35c480d2ecb995d92d9fd0..3970e8e8c2a310c0111ff0a35ec4c4266c56d19d 100644 (file)
@@ -1,8 +1,8 @@
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
-  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment inbox unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
@@ -19,8 +19,8 @@
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
   2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
   2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
   2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
   2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
-  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox unread)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
-  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox unread)
-  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (inbox unread)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
-  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
diff --git a/test/emacs-tree.expected-output/notmuch-tree-tag-inbox-oldest-first b/test/emacs-tree.expected-output/notmuch-tree-tag-inbox-oldest-first
new file mode 100644 (file)
index 0000000..4277eda
--- /dev/null
@@ -0,0 +1,53 @@
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (inbox unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+End of search results.
index 428c0ae8c3764c1fb4b4656f598a341a856c3e9a..eb62e14429af48bb3b1319257a683e617ab0e873 100644 (file)
@@ -1,8 +1,8 @@
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox test_tag unread)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox test_tag unread)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
-  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment inbox unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
@@ -19,8 +19,8 @@
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
   2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
   2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
   2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
   2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
-  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox unread)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
-  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox unread)
-  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (inbox unread)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
-  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
index 828c5251e55f3431f18c182e7391183166d85459..c90d726e3a8831b740e988e973fc0658c4e5ff1f 100644 (file)
@@ -1,8 +1,8 @@
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
-  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment inbox unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
@@ -19,8 +19,8 @@
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
   2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
   2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
   2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
   2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
-  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox unread)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed test_thread_tag unread)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed test_thread_tag unread)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed test_thread_tag unread)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed test_thread_tag unread)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed test_thread_tag unread)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed test_thread_tag unread)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox test_thread_tag unread)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed test_thread_tag unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox test_thread_tag unread)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox test_thread_tag unread)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed test_thread_tag unread)
   2009-11-18  Carl Worth            ╰─► ...                                               (inbox test_thread_tag unread)
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox unread)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
   2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
   2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
-  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox unread)
-  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (inbox unread)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
-  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
index 7eb246963744a08a8587a63282db02efc3202403..42285f0eaa3f99284dc20ea06f6a933524155732 100644 (file)
@@ -1,8 +1,8 @@
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (  ui)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (  ui)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (  ui)
   2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (  ui)
   2010-12-16  Olivier Berger        ─►Essai accentué                                      (  ui)
   2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (  ui)
-  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (& ui)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (  ui)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (& ui)
+  2009-11-18  Carl Worth            ╰─► ...                                               (  ui)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (  ui)
   2009-11-18  Carl Worth            ╰─► ...                                               (  ui)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        ( =ui)
   2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (  ui)
   2009-11-18  Carl Worth            ╰─► ...                                               (  ui)
   2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        ( =ui)
@@ -19,8 +19,8 @@
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (  ui)
   2009-11-18  Keith Packard         ╰┬► ...                                               (  ui)
   2009-11-18  Carl Worth             ╰─► ...                                              (  ui)
   2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (  ui)
   2009-11-18  Keith Packard         ╰┬► ...                                               (  ui)
   2009-11-18  Carl Worth             ╰─► ...                                              (  ui)
-  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (  ui)
-  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (  ui)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (  ui)
+  2009-11-18  Carl Worth            ╰─► ...                                               (  ui)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             ( = i)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               ( =ui)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              ( =ui)
   2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             ( = i)
   2009-11-17  Mikhail Gusarov       ├┬► ...                                               ( =ui)
   2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              ( =ui)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (  ui)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            ( =ui)
   2009-11-18  Carl Worth            ╰─► ...                                               (  ui)
   2009-11-17  Keith Packard         │ ╰┬► ...                                             (  ui)
   2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            ( =ui)
   2009-11-18  Carl Worth            ╰─► ...                                               (  ui)
-  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (   i)
-  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (  ui)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (   i)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (  ui)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (  ui)
   2009-11-17  Keith Packard          ╰┬► ...                                              (  ui)
   2009-11-18  Carl Worth              ╰─► ...                                             (  ui)
   2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (  ui)
   2009-11-17  Keith Packard          ╰┬► ...                                              (  ui)
   2009-11-18  Carl Worth              ╰─► ...                                             (  ui)
-  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (  ui)
-  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (  ui)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (  ui)
+  2009-11-18  Alexander Botero-Low  ╰─► ...                                               (  ui)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (  ui)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (  ui)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (  ui)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (  ui)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (  ui)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (  ui)
   2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (  ui)
   2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (  ui)
   2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (  ui)
   2009-11-18  Jjgod Jiang            ╰┬► ...                                              (  ui)
   2009-11-18  Alexander Botero-Low    ╰─► ...                                             (  ui)
   2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (  ui)
-  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (  ui)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (  ui)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (  ui)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (  ui)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (  ui)
   2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (  ui)
-  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (  ui)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (  ui)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (&=ui)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (&=ui)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (  ui)
   2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (&=ui)
   2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (&=ui)
   2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (  ui)
index bcb10b96c78c18ba6cbdf51c33e4028bdc705c99..91bbf69427444caebfb8f68fef8dccdf881b2d45 100644 (file)
   2009-11-18  Carl Worth          [notmuch] Working with Maildir storage?               (  ui)
   2009-11-18  Carl Worth          [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
   2009-11-18  Alexander Botero-Low[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap(  ui)
   2009-11-18  Carl Worth          [notmuch] Working with Maildir storage?               (  ui)
   2009-11-18  Carl Worth          [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
   2009-11-18  Alexander Botero-Low[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap(  ui)
-  2009-11-18  Keith Packard       [notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap(  ui)
+  2009-11-18  Keith Packard       [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap(  ui)
   2009-11-18  Alexander Botero-Low[notmuch] request for pull                            (  ui)
   2009-11-18  Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Jjgod Jiang         [notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Rolland Santimano   [notmuch] Link to mailing list archives ?             (  ui)
   2009-11-18  Alexander Botero-Low[notmuch] request for pull                            (  ui)
   2009-11-18  Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Jjgod Jiang         [notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Rolland Santimano   [notmuch] Link to mailing list archives ?             (  ui)
-  2009-11-18  Jan Janak           [notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags(  ui)
+  2009-11-18  Jan Janak           [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags(  ui)
   2009-11-18  Jjgod Jiang         [notmuch] Mac OS X/Darwin compatibility issues        (  ui)
   2009-11-18  Jjgod Jiang         [notmuch] Mac OS X/Darwin compatibility issues        (  ui)
-  2009-11-18  Stewart Smith       [notmuch] [PATCH] count_files: sort directory in inode order before  statting(  ui)
+  2009-11-18  Stewart Smith       [notmuch] [PATCH] count_files: sort directory in inode order before statting(  ui)
   2009-11-18  Keith Packard       [notmuch] archive                                     (  ui)
   2009-11-18  Keith Packard       [notmuch] Introducing myself                          (  ui)
   2009-11-18  Keith Packard       [notmuch] New to the list                             (  ui)
   2009-11-18  Stewart Smith       [notmuch] [PATCH 2/2] Read mail directory in inode number order(  ui)
   2009-11-18  Keith Packard       [notmuch] archive                                     (  ui)
   2009-11-18  Keith Packard       [notmuch] Introducing myself                          (  ui)
   2009-11-18  Keith Packard       [notmuch] New to the list                             (  ui)
   2009-11-18  Stewart Smith       [notmuch] [PATCH 2/2] Read mail directory in inode number order(  ui)
-  2009-11-18  Stewart Smith       [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs.(  ui)
+  2009-11-18  Stewart Smith       [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs.(  ui)
   2009-11-18  Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr?           (&=ui)
   2009-11-18  Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr?           (&=ui)
   2009-11-18  Lars Kellogg-Stedman[notmuch] Working with Maildir storage?               ( =ui)
   2009-11-18  Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr?           (&=ui)
   2009-11-18  Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr?           (&=ui)
   2009-11-18  Lars Kellogg-Stedman[notmuch] Working with Maildir storage?               ( =ui)
-  2009-11-18  Alex Botero-Lowry   [notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop(& ui)
+  2009-11-18  Alex Botero-Lowry   [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop(& ui)
   2009-11-17  Ingmar Vanhassel    [notmuch] [PATCH] Typsos                              (  ui)
   2009-11-17  Aron Griffis        [notmuch] archive                                     (  ui)
   2009-11-17  Adrian Perez de Cast[notmuch] Introducing myself                          ( =ui)
   2009-11-17  Ingmar Vanhassel    [notmuch] [PATCH] Typsos                              (  ui)
   2009-11-17  Aron Griffis        [notmuch] archive                                     (  ui)
   2009-11-17  Adrian Perez de Cast[notmuch] Introducing myself                          ( =ui)
@@ -37,7 +37,7 @@
   2009-11-17  Jan Janak           [notmuch] What a great idea!                          (  ui)
   2009-11-17  Jan Janak           [notmuch] What a great idea!                          (  ui)
   2009-11-17  Jan Janak           [notmuch] [PATCH] Older versions of install do not support -C.(  ui)
   2009-11-17  Jan Janak           [notmuch] What a great idea!                          (  ui)
   2009-11-17  Jan Janak           [notmuch] What a great idea!                          (  ui)
   2009-11-17  Jan Janak           [notmuch] [PATCH] Older versions of install do not support -C.(  ui)
-  2009-11-17  Keith Packard       [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags(  ui)
+  2009-11-17  Keith Packard       [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags(  ui)
   2009-11-17  Keith Packard       [notmuch] Working with Maildir storage?               (  ui)
   2009-11-17  Keith Packard       [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
   2009-11-17  Mikhail Gusarov     [notmuch] [PATCH] Handle rename of message file       (  ui)
   2009-11-17  Keith Packard       [notmuch] Working with Maildir storage?               (  ui)
   2009-11-17  Keith Packard       [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
   2009-11-17  Mikhail Gusarov     [notmuch] [PATCH] Handle rename of message file       (  ui)
@@ -48,6 +48,6 @@
   2009-11-17  Mikhail Gusarov     [notmuch] Working with Maildir storage?               ( =ui)
   2009-11-17  Lars Kellogg-Stedman[notmuch] Working with Maildir storage?               ( =ui)
   2009-11-17  Carl Worth          [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
   2009-11-17  Mikhail Gusarov     [notmuch] Working with Maildir storage?               ( =ui)
   2009-11-17  Lars Kellogg-Stedman[notmuch] Working with Maildir storage?               ( =ui)
   2009-11-17  Carl Worth          [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
-  2009-11-17  Mikhail Gusarov     [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++      file with gcc 4.4(  ui)
-  2009-11-17  Mikhail Gusarov     [notmuch] [PATCH 1/2] Close message file after parsing message       headers(  ui)
+  2009-11-17  Mikhail Gusarov     [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4(  ui)
+  2009-11-17  Mikhail Gusarov     [notmuch] [PATCH 1/2] Close message file after parsing message headers(  ui)
 End of search results.
 End of search results.
index 0a048e1fe66940b914182c78400ad4dea8a8a94c..9bc99a2cf3588fcb2dbc3ac32933b9e425ebd7f6 100644 (file)
@@ -24,7 +24,7 @@ find_builddir () {
 if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
        export NOTMUCH_BUILDDIR="$(find_builddir "$(pwd)")"
 
 if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
        export NOTMUCH_BUILDDIR="$(find_builddir "$(pwd)")"
 
-       if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+       if [ -z "${NOTMUCH_BUILDDIR}" -a "${NOTMUCH_TEST_INSTALLED-0}" = "0" ]; then
                echo "Run tests in a subdir of built notmuch tree." >&2
                exit 1
        fi
                echo "Run tests in a subdir of built notmuch tree." >&2
                exit 1
        fi
index 14747bdbfc44d040cc0118d3d84328a7c69e6e06..5d27e4d1d143c74daa4b3921a151876e8d441ce0 100755 (executable)
@@ -20,6 +20,14 @@ fi
 
 set -eu
 
 
 set -eu
 
+# Where to run the tests
+# XXX FIXME this code is duplicated with test-lib.sh
+if [[ -n "${NOTMUCH_BUILDDIR}" ]]; then
+    TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
+else
+    TEST_DIRECTORY=$NOTMUCH_SRCDIR/test
+fi
+
 TESTS=
 for test in ${NOTMUCH_TESTS-}; do
     TESTS="$TESTS $NOTMUCH_SRCDIR/test/$test"
 TESTS=
 for test in ${NOTMUCH_TESTS-}; do
     TESTS="$TESTS $NOTMUCH_SRCDIR/test/$test"
@@ -80,7 +88,7 @@ for file in $TESTS
 do
     file=${file##*/} # drop leading path components
     file=${file%.sh} # drop trailing '.sh'
 do
     file=${file##*/} # drop leading path components
     file=${file%.sh} # drop trailing '.sh'
-    RESULT_FILES="$RESULT_FILES $NOTMUCH_BUILDDIR/test/test-results/$file"
+    RESULT_FILES="$RESULT_FILES $TEST_DIRECTORY/test-results/$file"
 done
 
 echo
 done
 
 echo
@@ -94,6 +102,6 @@ if [ -n "$META_FAILURE" ]; then
 fi
 
 # Clean up
 fi
 
 # Clean up
-rm -rf $NOTMUCH_BUILDDIR/test/test-results
+rm -rf $TEST_DIRECTORY/test-results
 
 exit $ev
 
 exit $ev
index d8397714874cbc4236422aa61bdda53f7e322205..5d96be7f429f744b0ac50e7bbe7af20aa0ed015a 100644 (file)
@@ -1,18 +1,18 @@
 # .notmuch-config - Configuration file for the notmuch mail system
 #
 # For more information about notmuch, see https://notmuchmail.org
 # .notmuch-config - Configuration file for the notmuch mail system
 #
 # For more information about notmuch, see https://notmuchmail.org
-
 # Database configuration
 #
 # Database configuration
 #
-# The only value supported here is 'path' which should be the top-level
-# directory where your mail currently exists and to where mail will be
-# delivered in the future. Files should be individual email messages.
-# Notmuch will store its database within a sub-directory of the path
-# configured here named ".notmuch".
+# Supported values are 'mail_root' and 'path'. The recommended option
+# is to set 'mail_root' to the directory where your mail currently exists
+# and to where mail will be delivered in the future. Files should be
+# individual email messages. By default notmuch will store its database
+# in $XDG_DATA_HOME/notmuch; you can override this by setting 'path'.
+# If only 'path' is set, this directory is for 'mail_root' and for
+# the database location (in a subdirectory called ".notmuch").
 #
 [database]
 path=/path/to/maildir
 #
 [database]
 path=/path/to/maildir
-
 # User configuration
 #
 # Here is where you can let notmuch know how you would like to be
 # User configuration
 #
 # Here is where you can let notmuch know how you would like to be
@@ -32,7 +32,6 @@ path=/path/to/maildir
 name=Test Suite
 primary_email=test.suite@example.com
 other_email=another.suite@example.com
 name=Test Suite
 primary_email=test.suite@example.com
 other_email=another.suite@example.com
-
 # Configuration for "notmuch new"
 #
 # The following options are supported here:
 # Configuration for "notmuch new"
 #
 # The following options are supported here:
@@ -49,7 +48,6 @@ other_email=another.suite@example.com
 #
 [new]
 tags=foo;bar;
 #
 [new]
 tags=foo;bar;
-
 # Search configuration
 #
 # The following option is supported here:
 # Search configuration
 #
 # The following option is supported here:
@@ -61,7 +59,6 @@ tags=foo;bar;
 #
 [search]
 exclude_tags=baz
 #
 [search]
 exclude_tags=baz
-
 # Maildir compatibility configuration
 #
 # The following option is supported here:
 # Maildir compatibility configuration
 #
 # The following option is supported here:
index 18fa29c0dd86d3b7eb8ab5c3b69bd7f9a35cf89a..f5d72e12c5c4873700c7814bf550cd0510c1a15c 100644 (file)
@@ -24,7 +24,7 @@
 #
 type die >/dev/null 2>&1 || die () { echo "$@" >&2; exit 1; }
 
 #
 type die >/dev/null 2>&1 || die () { echo "$@" >&2; exit 1; }
 
-if [[ -z "$NOTMUCH_SRCDIR" ]] || [[ -z "$NOTMUCH_BUILDDIR" ]]; then
+if [[ -z "$NOTMUCH_SRCDIR" ]] || [ -z "${NOTMUCH_TEST_INSTALLED-}" -a -z "$NOTMUCH_BUILDDIR" ]; then
        echo "internal: srcdir or builddir not set" >&2
        exit 1
 fi
        echo "internal: srcdir or builddir not set" >&2
        exit 1
 fi
@@ -61,7 +61,9 @@ LD_LIBRARY_PATH=${TEST_DIRECTORY%/*}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
 export LD_LIBRARY_PATH
 
 # configure output
 export LD_LIBRARY_PATH
 
 # configure output
-. "$NOTMUCH_BUILDDIR/sh.config" || exit 1
+if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
+   . "$NOTMUCH_BUILDDIR/sh.config" || exit 1
+fi
 
 # load OS specifics
 if [[ -e "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" ]]; then
 
 # load OS specifics
 if [[ -e "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" ]]; then
@@ -315,7 +317,12 @@ export PATH MANPATH
 
 # Test repository
 test="tmp.$(basename "$0" .sh)"
 
 # Test repository
 test="tmp.$(basename "$0" .sh)"
-TMP_DIRECTORY="$TEST_DIRECTORY/$test"
+if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
+    TMP_DIRECTORY="$TEST_DIRECTORY/$test"
+else
+    TMP_DIRECTORY=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-$test.XXXXXX")
+fi
+
 test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY
 rm -rf "$TMP_DIRECTORY" || {
        GIT_EXIT_OK=t
 test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY
 rm -rf "$TMP_DIRECTORY" || {
        GIT_EXIT_OK=t
@@ -323,6 +330,10 @@ rm -rf "$TMP_DIRECTORY" || {
        exit 1
 }
 
        exit 1
 }
 
+# Provide a guess at a usable Python, to support running tests without
+# running configure first.
+NOTMUCH_PYTHON=${NOTMUCH_PYTHON-python3}
+
 # A temporary home directory is needed by at least:
 # - emacs/"Sending a message via (fake) SMTP"
 # - emacs/"Reply within emacs"
 # A temporary home directory is needed by at least:
 # - emacs/"Sending a message via (fake) SMTP"
 # - emacs/"Reply within emacs"
index ad4c4aeb621afd02d5d3b4cf71b34cbe2f5b6d4e..0ab58fc2c4f12c5b311b47f359edf7b90987aa8a 100644 (file)
@@ -30,6 +30,7 @@ test_require_emacs () {
 # to the message and encrypting/signing.
 emacs_deliver_message () {
     local subject body smtp_dummy_pid smtp_dummy_port
 # to the message and encrypting/signing.
 emacs_deliver_message () {
     local subject body smtp_dummy_pid smtp_dummy_port
+    test_subtest_broken_for_installed
     subject="$1"
     body="$2"
     shift 2
     subject="$1"
     body="$2"
     shift 2
@@ -144,6 +145,13 @@ emacs_generate_script () {
        # Construct a little test script here for the benefit of the user,
        # (who can easily run "run_emacs" to get the same emacs environment
        # for investigating any failures).
        # Construct a little test script here for the benefit of the user,
        # (who can easily run "run_emacs" to get the same emacs environment
        # for investigating any failures).
+    if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
+       find_notmuch_el='--directory "$NOTMUCH_BUILDDIR/emacs"'
+    else
+       ### XXX FIXME: this should really use the installed emacs lisp files
+       find_notmuch_el='--directory "$NOTMUCH_SRCDIR/emacs"'
+    fi
+
        cat <<EOF >"$TMP_DIRECTORY/run_emacs"
 #!/bin/sh
 export PATH=$PATH
        cat <<EOF >"$TMP_DIRECTORY/run_emacs"
 #!/bin/sh
 export PATH=$PATH
@@ -158,8 +166,8 @@ export NOTMUCH_CONFIG=$NOTMUCH_CONFIG
 #
 # --load               Force loading of notmuch.el and test-lib.el
 
 #
 # --load               Force loading of notmuch.el and test-lib.el
 
-exec ${TEST_EMACS} --quick \
-       --directory "$NOTMUCH_BUILDDIR/emacs" --load notmuch.el \
+exec ${TEST_EMACS} ${find_notmuch_el} --quick \
+       ${EXTRA_DIR} --load notmuch.el \
        --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \
        "\$@"
 EOF
        --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \
        "\$@"
 EOF
index 79a9d4d6fc5b574cbd21e6e9bd27039689a2d01a..bf1fab66ff549286b515ba480cb9d2fe537ff66b 100644 (file)
 
 ;;; Code:
 
 
 ;;; Code:
 
+;; minimize impact of native compilation on the test suite.
+;; These are the Emacs 29.1 version of the variables.
+;; Leave trampolines enabled per Emacs upstream recommendations.
+;; It is important to set these variables before loading any
+;; .elc files.
+(setq native-comp-jit-compilation nil)
+(setq native-comp-speed -1)
+(setq native-comp-async-jobs-number 1)
+
 (require 'cl-lib)
 
 (require 'cl-lib)
 
+;; Use old pretty print algorithm, so tests don't break with Emacs 30
+(setq-default pp-default-function 'pp-28)
+
 ;; Ensure that the dynamic variables that are defined by this library
 ;; are defined by the time that we let-bind them.  This is needed
 ;; because starting with Emacs 27 undeclared variables in evaluated
 ;; Ensure that the dynamic variables that are defined by this library
 ;; are defined by the time that we let-bind them.  This is needed
 ;; because starting with Emacs 27 undeclared variables in evaluated
@@ -205,3 +217,8 @@ running, quit if it terminated."
 ;; environments
 
 (setq mm-text-html-renderer 'html2text)
 ;; environments
 
 (setq mm-text-html-renderer 'html2text)
+
+;; Set our own default for message-hidden-headers, to avoid tests
+;; breaking when the Emacs default changes.
+(setq message-hidden-headers
+      '("^References:" "^Face:" "^X-Face:" "^X-Draft-From:"))
index eec5c5b4508c1224d7b2510fe3822ce13e374bc0..5b3cd454854f83267ebd5a758f31e67295c7ce94 100644 (file)
@@ -29,8 +29,8 @@ shopt -u xpg_echo
 # Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
 . $(dirname "$0")/export-dirs.sh || exit 1
 
 # Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
 . $(dirname "$0")/export-dirs.sh || exit 1
 
-# It appears that people try to run tests without building...
-if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
+# We need either a built tree, or a promise of an installed notmuch
+if [ -z "${NOTMUCH_TEST_INSTALLED-}" -a ! -x "$NOTMUCH_BUILDDIR/notmuch" ]; then
        echo >&2 'You do not seem to have built notmuch yet.'
        exit 1
 fi
        echo >&2 'You do not seem to have built notmuch yet.'
        exit 1
 fi
@@ -388,6 +388,14 @@ test_expect_equal_message_body () {
     test "$#" = 2 ||
        error "bug in the test script: not 2 parameters to test_expect_equal_file"
 
     test "$#" = 2 ||
        error "bug in the test script: not 2 parameters to test_expect_equal_file"
 
+    for file in "$1" "$2"; do
+       if [ ! -s "$file" ]; then
+           test_failure_ "Missing or zero length file: $file"
+           inside_subtest=
+           return 1
+       fi
+    done
+
     expected=$(sed '1,/^$/d' "$1")
     output=$(sed '1,/^$/d' "$2")
     test_expect_equal "$expected" "$output"
     expected=$(sed '1,/^$/d' "$1")
     output=$(sed '1,/^$/d' "$2")
     test_expect_equal "$expected" "$output"
@@ -740,6 +748,18 @@ test_subtest_known_broken () {
        test_subtest_known_broken_=t
 }
 
        test_subtest_known_broken_=t
 }
 
+test_subtest_broken_for_installed () {
+    if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+       test_subtest_known_broken_=t
+    fi
+}
+
+test_subtest_broken_for_root () {
+   if [ "$EUID" = "0" ]; then
+       test_subtest_known_broken_=t
+    fi
+}
+
 test_expect_success () {
        exec 1>&6 2>&7          # Restore stdout and stderr
        if [ -z "$inside_subtest" ]; then
 test_expect_success () {
        exec 1>&6 2>&7          # Restore stdout and stderr
        if [ -z "$inside_subtest" ]; then
@@ -876,7 +896,7 @@ test_done () {
 test_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')
 test_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="$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage:$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
+    PYTHONPATH="$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage${PYTHONPATH:+:$PYTHONPATH}" \
        $NOTMUCH_PYTHON -B - > OUTPUT
 }
 
        $NOTMUCH_PYTHON -B - > OUTPUT
 }
 
@@ -915,11 +935,16 @@ make_shim () {
 }
 
 notmuch_with_shim () {
 }
 
 notmuch_with_shim () {
-    local base_name shim_file
-    base_name="$1"
+    local base_name shim_file notmuch_cmd
+    if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
+       notmuch_cmd="notmuch"
+    else
+       notmuch_cmd="notmuch-shared"
+    fi
+    base_name=$1
     shift
     shim_file="${base_name}.so"
     shift
     shim_file="${base_name}.so"
-    LD_PRELOAD=${LD_PRELOAD:+:$LD_PRELOAD}:./${shim_file} notmuch-shared "$@"
+    LD_PRELOAD=${LD_PRELOAD:+:$LD_PRELOAD}:./${shim_file} $notmuch_cmd "$@"
 }
 
 # Creates a script that counts how much time it is executed and calls
 }
 
 # Creates a script that counts how much time it is executed and calls
@@ -971,7 +996,11 @@ test_init_ () {
 
 
 # Where to run the tests
 
 
 # Where to run the tests
-TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
+if [[ -n "${NOTMUCH_BUILDDIR}" ]]; then
+    TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
+else
+    TEST_DIRECTORY=$NOTMUCH_SRCDIR/test
+fi
 
 . "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
 
 
 . "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
 
diff --git a/util/xapian-extra.h b/util/xapian-extra.h
new file mode 100644 (file)
index 0000000..39c7f48
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef _XAPIAN_EXTRA_H
+#define _XAPIAN_EXTRA_H
+
+#include <string>
+#include <xapian.h>
+
+inline Xapian::Query
+xapian_query_match_all (void)
+{
+    // Xapian::Query::MatchAll isn't thread safe (a static object with reference
+    // counting) so instead reconstruct the equivalent on demand.
+    return Xapian::Query (std::string ());
+}
+
+#endif
index c128d4d9e49568bf12a01322b7981ebbfdb2c058..751b1ea05ff17e0aa87ae2f4be9b82d41dc8da78 100644 (file)
@@ -1 +1 @@
-0.37
+0.39
index c137bacdc0f4f1e3af78cb9617d5fe92f527c53f..777c20c0e6f0b8a695fe2d997ddb7284660e5de2 100644 (file)
@@ -53,10 +53,10 @@ Enjoy ;)
 As an example to configure a key mapping to add the tag 'to-do' and archive,
 this is what I use:
 
 As an example to configure a key mapping to add the tag 'to-do' and archive,
 this is what I use:
 
-let g:notmuch_rb_custom_search_maps = {
+let g:notmuch_custom_search_maps = {
        \ 't':          'search_tag("+to-do -inbox")',
        \ }
 
        \ 't':          'search_tag("+to-do -inbox")',
        \ }
 
-let g:notmuch_rb_custom_show_maps = {
+let g:notmuch_custom_show_maps = {
        \ 't':          'show_tag("+to-do -inbox")',
        \ }
        \ 't':          'show_tag("+to-do -inbox")',
        \ }
index 43741022e13580cafd7f1190a601608b14afcafe..c98f2b53d80df5c50fa13142a73ebc53b50e5075 100644 (file)
@@ -89,7 +89,7 @@ s     Send
 CONFIGURATION                                  *notmuch-config*
 
 You can add the following configurations to your `.vimrc`, or
 CONFIGURATION                                  *notmuch-config*
 
 You can add the following configurations to your `.vimrc`, or
-`~/.vim/plugin/notmuch.vim`.
+`~/.vim/after/plugin/notmuch.vim`.
 
                                                *g:notmuch_folders*
 
 
                                                *g:notmuch_folders*
 
@@ -138,13 +138,13 @@ You can do the same for the thread view:
 
 If you want to count the threads instead of the messages in the folder view:
 >
 
 If you want to count the threads instead of the messages in the folder view:
 >
-       let g:notmuch_folders_count_threads = 0
+       let g:notmuch_folders_count_threads = 1
 <
 
                                                *g:notmuch_reader*
                                                *g:notmuch_sendmail*
 
 <
 
                                                *g:notmuch_reader*
                                                *g:notmuch_sendmail*
 
-You can also configure your externail mail reader and sendemail program:
+You can also configure your external mail reader and sendmail program:
 >
        let g:notmuch_reader = 'mutt -f %s'
        let g:notmuch_sendmail = 'sendmail'
 >
        let g:notmuch_reader = 'mutt -f %s'
        let g:notmuch_sendmail = 'sendmail'