aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Bremner <bremner@debian.org>2012-01-27 18:04:44 -0400
committerDavid Bremner <bremner@debian.org>2012-01-27 18:04:44 -0400
commitbcd44490684965082e5921d0fc71064af3170987 (patch)
tree12506f79f6792236b95ef0d6a19ab5113bb4587d
parentbfb7feb1f51ae348503046823d72348621e34d08 (diff)
parentffce9b7c25b9b44ec026b67d96e44cae09c99efe (diff)
Merge commit 'debian/0.11-1' into squeeze-backports
Conflicts: debian/changelog
-rw-r--r--.gitignore1
-rw-r--r--Makefile.local62
-rw-r--r--NEWS104
-rw-r--r--RELEASING6
-rw-r--r--bindings/python/docs/source/index.rst17
-rw-r--r--bindings/python/notmuch/__init__.py2
-rw-r--r--bindings/python/notmuch/database.py216
-rw-r--r--bindings/python/notmuch/filename.py25
-rw-r--r--bindings/python/notmuch/globals.py88
-rw-r--r--bindings/python/notmuch/message.py143
-rw-r--r--bindings/python/notmuch/tag.py39
-rw-r--r--bindings/python/notmuch/thread.py99
-rw-r--r--bindings/python/notmuch/version.py2
-rw-r--r--command-line-arguments.c155
-rw-r--r--command-line-arguments.h80
-rw-r--r--contrib/.gitattributes1
-rw-r--r--contrib/notmuch-deliver/COPYING340
-rw-r--r--contrib/notmuch-deliver/README.mkd8
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/Makefile.am1
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/configure.in2
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/maildircreate.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/maildircreate.h1
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h1
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/maildir/maildiropen.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/Makefile.am1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/atotimet.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/atouidt.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/configure.in2
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/numlib.h1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strdevt.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strgidt.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strhdevt.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strhinot.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strhpidt.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strhtimet.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strinot.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strofft.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strpidt.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strsize.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strsizet.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/strtimet.c1
-rw-r--r--contrib/notmuch-deliver/maildrop/numlib/struidt.c1
-rw-r--r--contrib/notmuch-deliver/src/main.c29
-rw-r--r--debian/changelog29
-rw-r--r--debian/control2
-rw-r--r--debian/gbp.conf5
-rw-r--r--emacs/notmuch-address.el21
-rw-r--r--emacs/notmuch-crypto.el9
-rw-r--r--emacs/notmuch-hello.el140
-rw-r--r--emacs/notmuch-maildir-fcc.el12
-rw-r--r--emacs/notmuch-message.el8
-rw-r--r--emacs/notmuch-mua.el20
-rw-r--r--emacs/notmuch-show.el119
-rw-r--r--emacs/notmuch.el92
-rw-r--r--hooks.c93
-rw-r--r--lib/Makefile.local2
-rw-r--r--lib/database.cc17
-rw-r--r--lib/message.cc14
-rw-r--r--notmuch-client.h4
-rw-r--r--notmuch-config.c119
-rw-r--r--notmuch-dump.c38
-rw-r--r--notmuch-new.c14
-rw-r--r--notmuch-reply.c10
-rw-r--r--notmuch-restore.c37
-rw-r--r--notmuch-search.c106
-rw-r--r--notmuch-show.c11
-rw-r--r--notmuch-tag.c87
-rw-r--r--notmuch.152
-rw-r--r--notmuch.c39
-rw-r--r--test/.gitignore2
-rw-r--r--test/Makefile.local17
-rw-r--r--test/README19
-rw-r--r--test/arg-test.c52
-rwxr-xr-xtest/argument-parsing16
-rwxr-xr-xtest/atomicity12
-rwxr-xr-xtest/basic14
-rwxr-xr-xtest/emacs131
-rw-r--r--test/emacs.expected-output/emacs-stashing9
-rw-r--r--test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation215
-rw-r--r--test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation215
-rwxr-xr-xtest/hooks104
-rwxr-xr-xtest/json2
-rwxr-xr-xtest/notmuch-test7
-rwxr-xr-xtest/python6
-rwxr-xr-xtest/raw2
-rwxr-xr-xtest/symbol-hiding11
-rw-r--r--test/symbol-test.cc14
-rw-r--r--test/test-lib.el15
-rw-r--r--[-rwxr-xr-x]test/test-lib.sh165
-rw-r--r--util/Makefile.local2
-rw-r--r--version2
93 files changed, 2542 insertions, 1035 deletions
diff --git a/.gitignore b/.gitignore
index 9468e30e..d64ec9f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ notmuch.sym
notmuch-shared
notmuch.1.gz
libnotmuch.so*
+libnotmuch*.dylib
*.[ao]
*~
.*.swp
diff --git a/Makefile.local b/Makefile.local
index d97fa618..97f397ff 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -12,16 +12,18 @@ PACKAGE=notmuch
IS_GIT=$(shell if [ -d .git ] ; then echo yes ; else echo no; fi)
+ifeq ($(IS_GIT),yes)
+DATE:=$(shell git log --date=short -1 --pretty=format:%cd)
+else
+DATE:=$(shell date +%F)
+endif
+
VERSION:=$(shell cat ${srcdir}/version)
-ifneq ($(MAKECMDGOALS),release)
-ifneq ($(MAKECMDGOALS),release-message)
-ifneq ($(MAKECMDGOALS),pre-release)
+ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),)
ifeq ($(IS_GIT),yes)
VERSION:=$(shell git describe --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/)
endif
endif
-endif
-endif
UPSTREAM_TAG=$(subst ~,_,$(VERSION))
DEB_TAG=debian/$(UPSTREAM_TAG)-1
@@ -91,6 +93,12 @@ $(GPG_FILE): $(SHA1_FILE)
.PHONY: dist
dist: $(TAR_FILE)
+.PHONY: update-versions
+
+update-versions:
+ sed -i "s/^.TH NOTMUCH 1.*$$/.TH NOTMUCH 1 ${DATE} \"Notmuch ${VERSION}\"/" notmuch.1
+ sed -i "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" $(PV_FILE)
+
# We invoke make recursively only to force ordering of our phony
# targets in the case of parallel invocation of make (-j).
#
@@ -114,7 +122,7 @@ release: verify-source-tree-and-version
ifeq ($(REALLY_UPLOAD),yes)
git push origin $(VERSION)
cd releases && scp $(TAR_FILE) $(SHA1_FILE) $(GPG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
- ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-[0-9]* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(PACKAGE)-$(VERSION)"
+ ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)"
endif
@echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template."
@@ -131,15 +139,16 @@ pre-release:
mv $(TAR_FILE) $(DEB_TAR_FILE) releases
.PHONY: debian-snapshot
-debian-snapshot: TMPFILE := $(shell mktemp)
debian-snapshot:
make VERSION=$(VERSION) clean
- cp debian/changelog $(TMPFILE)
- EDITOR=/bin/true dch -b -v $(VERSION)+1 -D UNRELEASED 'test build, not for upload'
- echo '3.0 (native)' > debian/source/format
- debuild -us -uc
- mv -f $(TMPFILE) debian/changelog
- echo '3.0 (quilt)' > debian/source/format
+ TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX); \
+ cp debian/changelog $${TMPFILE}; \
+ EDITOR=/bin/true dch -b -v $(VERSION)+1 \
+ -D UNRELEASED 'test build, not for upload'; \
+ echo '3.0 (native)' > debian/source/format; \
+ debuild -us -uc; \
+ mv -f $${TMPFILE} debian/changelog; \
+ echo '3.0 (quilt)' > debian/source/format
.PHONY: release-message
release-message:
@@ -179,7 +188,7 @@ release-message:
verify-source-tree-and-version: verify-no-dirty-code
.PHONY: verify-no-dirty-code
-verify-no-dirty-code: verify-version-debian verify-version-python
+verify-no-dirty-code: verify-version-debian verify-version-python verify-version-manpage
ifeq ($(IS_GIT),yes)
@printf "Checking that source tree is clean..."
ifneq ($(shell git ls-files -m),)
@@ -199,28 +208,33 @@ endif
.PHONY: verify-version-debian
verify-version-debian: verify-version-components
@echo -n "Checking that Debian package version is $(VERSION)-1..."
- @if [ "$(VERSION)-1" != $$(dpkg-parsechangelog | grep ^Version | awk '{print $$2}') ] ; then \
+ @[ "$(VERSION)-1" = $$(sed '1{ s/).*//; s/.*(//; q; }' debian/changelog) ] || \
(echo "No." && \
- echo "Please edit version and debian/changelog to have consistent versions." && false) \
- fi
+ echo "Please edit version and debian/changelog to have consistent versions." && false)
@echo "Good."
.PHONY: verify-version-python
verify-version-python: verify-version-components
@echo -n "Checking that python bindings version is $(VERSION)..."
- @if [ "$(VERSION)" != $$(python -c "execfile('$(PV_FILE)'); print __VERSION__") ] ; then \
+ @[ "$(VERSION)" = $$(python -c "execfile('$(PV_FILE)'); print __VERSION__") ] || \
+ (echo "No." && \
+ echo "Please edit version and $(PV_FILE) to have consistent versions." && false)
+ @echo "Good."
+
+.PHONY: verify-version-manpage
+verify-version-manpage: verify-version-components
+ @echo -n "Checking that manual page version is $(VERSION)..."
+ @[ "$(VERSION)" = $$(sed -n '/^[.]TH NOTMUCH 1/{s/.*"Notmuch //;s/".*//p;}' notmuch.1) ] || \
(echo "No." && \
- echo "Please edit version and $(PV_FILE) to have consistent versions." && false) \
- fi
+ echo "Please edit version and notmuch.1 to have consistent versions." && false)
@echo "Good."
.PHONY: verify-version-components
verify-version-components:
@echo -n "Checking that $(VERSION) consists only of digits and periods..."
- @if echo $(VERSION) | grep -q -v -x '[0-9.]*'; then \
+ @echo $(VERSION) | grep -q -x '^[0-9.]*$$' || \
(echo "No." && \
- echo "Please follow the instructions in RELEASING to choose a version" && false) \
- else :; fi
+ echo "Please follow the instructions in RELEASING to choose a version" && false)
@echo "Good."
.PHONY: verify-newer
@@ -282,9 +296,11 @@ clean:
distclean: clean
notmuch_client_srcs = \
+ command-line-arguments.c\
debugger.c \
gmime-filter-reply.c \
gmime-filter-headers.c \
+ hooks.c \
notmuch.c \
notmuch-config.c \
notmuch-count.c \
diff --git a/NEWS b/NEWS
index 3f577e42..bf21e644 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,107 @@
+Notmuch 0.11 (2012-01-13)
+=========================
+
+Command-Line Interface
+----------------------
+
+Hooks
+
+ Hooks have been introduced to notmuch. Hooks are scripts that notmuch
+ invokes before and after certain actions. Initially, "notmuch new"
+ supports "pre-new" and "post-new" hooks that are run before and after
+ importing new messages into the database.
+
+notmuch reply --decrypt bugfix
+
+ The "notmuch reply" command with --decrypt argument had a rarely
+ occurring bug that caused an encrypted message not to be decrypted
+ sometimes. This is now fixed.
+
+Performance
+-----------
+
+Automatic tag query optimization
+
+ "notmuch tag" now automatically optimizes the user's query to
+ exclude messages whose tags won't change. In the past, we've
+ suggested that people do this by hand; this is no longer necessary.
+
+Don't sort messages when creating a dump file
+
+ This speeds up tag dumps considerably, without any loss of
+ information. To replicate the old behavior of sorted output (for
+ example to compare two dump files), one can use e.g. sort(1).
+
+Memory Management
+-----------------
+
+Reduction of memory leaks
+
+ Two memory leaks when searching and showing messages were identified
+ and fixed in this release.
+
+Emacs Interface
+---------------
+
+Bug fixes
+
+ notmuch-show-advance (bound to the spacebar in notmuch-show-mode) had
+ a bug that caused it to always jump to the next message, even if it
+ should have scrolled down to show more of the current message instead.
+ This is now fixed.
+
+Support "notmuch new" as a notmuch-poll-script
+
+ It's now possible to use "notmuch new" as a notmuch-poll-script
+ directly. This is also the new default. This allows taking better
+ advantage of the "notmuch new" hooks from emacs without intermediate
+ scripts.
+
+Improvements in saved search management
+
+ New saved searches are now appended to the list of saved searches,
+ not inserted in front. It's also possible to define a sort function
+ for displaying saved searches; alphabetical sort is provided.
+
+Hooks for notmuch-hello
+
+ Two new hooks have been added: "notmuch-hello-mode-hook" (called after
+ entering notmuch-hello-mode) and "notmuch-hello-refresh-hook" (called
+ after updating a notmuch-hello buffer).
+
+New face for crypto parts headers
+
+ Crypto parts used to be displayed with a hardcoded color. A new face
+ has been introduced to fix this: notmuch-crypto-part-header. It
+ defaults to the same value as before, but can be customized to match
+ other color themes.
+
+Use space as default thousands separator
+
+ Large numbers in notmuch-hello are now displayed using a space as
+ thousands separator (e.g. "123 456" instead of "123,456"). This can be
+ changed by customizing "notmuch-hello-thousands-separator".
+
+Call notmuch-show instead of notmuch-search when clicking on
+buttonized id: links.
+
+New function notmuch-show-advance
+
+ This new function advances through just the current thread, and is
+ less invasive than notmuch-show-advance-and-archive. It can easily
+ be bound to SPC with:
+
+ (define-key notmuch-show-mode-map " " 'notmuch-show-advance)
+
+Various performance improvements.
+
+New add-on tool
+---------------
+
+The tool contrib/notmuch-deliver helps with initial delivery and
+tagging of mail (replacing running notmuch new).
+
+
Notmuch 0.10.2 (2011-12-04)
===========================
diff --git a/RELEASING b/RELEASING
index e3e0cefe..88dab04e 100644
--- a/RELEASING
+++ b/RELEASING
@@ -62,11 +62,11 @@ repository. From here, there are just a few steps to release:
be "1.0.1" and a subsequent bug-fix release would be "1.0.2"
etc.
- Update bindings/python/notmuch/version.py to match version.
+ When you are happy with the file 'version', run
- Update the version in notmuch.1 to match version.
+ make update-versions
- XXX: Probably these last two steps should be (semi-)automated.
+ to propagate the version to the other places needed.
Commit these changes.
diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst
index 73d2a3b0..f7d3d605 100644
--- a/bindings/python/docs/source/index.rst
+++ b/bindings/python/docs/source/index.rst
@@ -138,10 +138,10 @@ More information on specific topics can be found on the following pages:
.. method:: __len__()
- .. warning:: :meth:`__len__` was removed in version 0.6 as it exhausted
- the iterator and broke list(Messages()). Use the
- :meth:`Query.count_messages` function or use
- `len(list(msgs))`.
+ .. warning::
+
+ :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
+ list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`.
:class:`Message` -- A single message
----------------------------------------
@@ -205,10 +205,11 @@ More information on specific topics can be found on the following pages:
.. method:: __len__
- .. warning:: :meth:`__len__` was removed in version 0.6 as it
- exhausted the iterator and broke list(Tags()). Use
- :meth:`len(list(msgs))` instead if you need to know the
- number of tags.
+ .. 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/notmuch/__init__.py b/bindings/python/notmuch/__init__.py
index 539afedf..f3ff9874 100644
--- a/bindings/python/notmuch/__init__.py
+++ b/bindings/python/notmuch/__init__.py
@@ -69,7 +69,7 @@ from notmuch.globals import (
TagTooLongError,
UnbalancedFreezeThawError,
UnbalancedAtomicError,
- NotInitializedError
+ NotInitializedError,
)
from notmuch.version import __VERSION__
__LICENSE__ = "GPL v3+"
diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py
index f4bc53e0..7923f768 100644
--- a/bindings/python/notmuch/database.py
+++ b/bindings/python/notmuch/database.py
@@ -18,13 +18,16 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
"""
import os
-from ctypes import c_int, c_char_p, c_void_p, c_uint, c_long, byref
+from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER
from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError,
- NullPointerError, OutOfMemoryError, XapianError, Enum, _str)
+ NullPointerError, Enum, _str,
+ NotmuchDatabaseP, NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP,
+ NotmuchQueryP, NotmuchMessagesP, NotmuchThreadsP, NotmuchFilenamesP)
from notmuch.thread import Threads
from notmuch.message import Messages, Message
from notmuch.tag import Tags
+
class Database(object):
"""The :class:`Database` is the highest-level object that notmuch
provides. It references a notmuch database, and can be opened in
@@ -37,16 +40,19 @@ class Database(object):
:exc:`XapianError` as the underlying database has been
modified. Close and reopen the database to continue working with it.
- .. note:: Any function in this class can and will throw an
- :exc:`NotInitializedError` if the database was not
- intitialized properly.
+ .. note::
+
+ Any function in this class can and will throw an
+ :exc:`NotInitializedError` if the database was not intitialized
+ properly.
+
+ .. note::
- .. note:: Do remember that as soon as we tear down (e.g. via `del
- db`) this object, all underlying derived objects such as
- queries, 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.
+ Do remember that as soon as we tear down (e.g. via `del db`) this
+ object, all underlying derived objects such as queries, 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.
"""
_std_db_path = None
"""Class attribute to cache user's default database"""
@@ -56,37 +62,50 @@ class Database(object):
"""notmuch_database_get_directory"""
_get_directory = nmlib.notmuch_database_get_directory
- _get_directory.restype = c_void_p
+ _get_directory.argtypes = [NotmuchDatabaseP, c_char_p]
+ _get_directory.restype = NotmuchDirectoryP
"""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_open"""
_open = nmlib.notmuch_database_open
- _open.restype = c_void_p
+ _open.argtypes = [c_char_p, c_uint]
+ _open.restype = NotmuchDatabaseP
"""notmuch_database_upgrade"""
_upgrade = nmlib.notmuch_database_upgrade
- _upgrade.argtypes = [c_void_p, c_void_p, c_void_p]
+ _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.restype = c_void_p
+ _get_all_tags.argtypes = [NotmuchDatabaseP]
+ _get_all_tags.restype = NotmuchTagsP
"""notmuch_database_create"""
_create = nmlib.notmuch_database_create
- _create.restype = c_void_p
+ _create.argtypes = [c_char_p]
+ _create.restype = NotmuchDatabaseP
def __init__(self, path=None, create=False, mode=0):
"""If *path* is `None`, we will try to read a users notmuch
@@ -164,8 +183,8 @@ class Database(object):
:param status: Open the database in read-only or read-write mode
:type status: :attr:`MODE`
:returns: Nothing
- :exception: Raises :exc:`NotmuchError` in case
- of any failure (possibly after printing an error message on stderr).
+ :exception: Raises :exc:`NotmuchError` in case of any failure
+ (possibly after printing an error message on stderr).
"""
res = Database._open(_str(path), mode)
@@ -186,6 +205,10 @@ class Database(object):
self._assert_db_is_initialized()
return Database._get_version(self._db)
+ _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?
@@ -197,7 +220,7 @@ class Database(object):
:returns: `True` or `False`
"""
self._assert_db_is_initialized()
- return nmlib.notmuch_database_needs_upgrade(self._db)
+ return self._needs_upgrade(self._db)
def upgrade(self):
"""Upgrades the current database
@@ -219,6 +242,10 @@ class Database(object):
#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
@@ -229,18 +256,20 @@ class Database(object):
neither begin nor end necessarily flush modifications to disk.
:returns: :attr:`STATUS`.SUCCESS or raises
-
- :exception: :exc:`NotmuchError`:
- :attr:`STATUS`.XAPIAN_EXCEPTION
- Xapian exception occurred; atomic section not entered.
+ :exception: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
+ Xapian exception occurred; atomic section not entered.
*Added in notmuch 0.9*"""
self._assert_db_is_initialized()
- status = nmlib.notmuch_database_begin_atomic(self._db)
+ 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
@@ -258,7 +287,7 @@ class Database(object):
*Added in notmuch 0.9*"""
self._assert_db_is_initialized()
- status = nmlib.notmuch_database_end_atomic(self._db)
+ status = self._end_atomic(self._db)
if status != STATUS.SUCCESS:
raise NotmuchError(status)
return status
@@ -267,13 +296,15 @@ class Database(object):
"""Returns a :class:`Directory` of path,
(creating it if it does not exist(?))
- .. warning:: This call needs a writeable database in
- :attr:`Database.MODE`.READ_WRITE mode. The underlying library will exit the
- program if this method is used on a read-only database!
+ .. warning::
+
+ This call needs a writeable database in
+ :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
+ exit the program if this method is used on a read-only database!
:param path: An unicode string containing the path relative to the path
- of database (see :meth:`get_path`), or else should be an absolute path
- with initial components that match the path of 'database'.
+ of database (see :meth:`get_path`), or else should be an absolute
+ path with initial components that match the path of 'database'.
:returns: :class:`Directory` or raises an exception.
:exception:
:exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR
@@ -299,6 +330,11 @@ class Database(object):
# return the Directory, init it with the absolute path
return Directory(_str(abs_dirpath), dir_p, self)
+ _add_message = nmlib.notmuch_database_add_message
+ _add_message.argtypes = [NotmuchDatabaseP, c_char_p,
+ POINTER(NotmuchMessageP)]
+ _add_message.restype = c_uint
+
def add_message(self, filename, sync_maildir_flags=False):
"""Adds a new message to the database
@@ -349,10 +385,8 @@ class Database(object):
be added.
"""
self._assert_db_is_initialized()
- msg_p = c_void_p()
- status = nmlib.notmuch_database_add_message(self._db,
- _str(filename),
- byref(msg_p))
+ msg_p = NotmuchMessageP()
+ status = self._add_message(self._db, _str(filename), byref(msg_p))
if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
raise NotmuchError(status)
@@ -364,6 +398,10 @@ class Database(object):
msg.maildir_flags_to_tags()
return (msg, status)
+ _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
@@ -392,8 +430,7 @@ class Database(object):
removed.
"""
self._assert_db_is_initialized()
- return nmlib.notmuch_database_remove_message(self._db,
- filename)
+ return self._remove_message(self._db, filename)
def find_message(self, msgid):
"""Returns a :class:`Message` as identified by its message ID
@@ -416,7 +453,7 @@ class Database(object):
the database was not intitialized.
"""
self._assert_db_is_initialized()
- msg_p = c_void_p()
+ msg_p = NotmuchMessageP()
status = Database._find_message(self._db, _str(msgid), byref(msg_p))
if status != STATUS.SUCCESS:
raise NotmuchError(status)
@@ -425,10 +462,11 @@ class Database(object):
def find_message_by_filename(self, filename):
"""Find a message with the given filename
- .. warning:: This call needs a writeable database in
- :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
- exit the program if this method is used on a read-only
- database!
+ .. warning::
+
+ This call needs a writeable database in
+ :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
+ exit the program if this method is used on a read-only database!
:returns: If the database contains a message with the given
filename, then a class:`Message:` is returned. This
@@ -449,7 +487,7 @@ class Database(object):
*Added in notmuch 0.9*"""
self._assert_db_is_initialized()
- msg_p = c_void_p()
+ msg_p = NotmuchMessageP()
status = Database._find_message_by_filename(self._db, _str(filename),
byref(msg_p))
if status != STATUS.SUCCESS:
@@ -460,7 +498,8 @@ class Database(object):
"""Returns :class:`Tags` with a list of all tags found in the database
:returns: :class:`Tags`
- :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER on error
+ :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
+ on error
"""
self._assert_db_is_initialized()
tags_p = Database._get_all_tags(self._db)
@@ -491,10 +530,14 @@ class Database(object):
def __repr__(self):
return "'Notmuch DB " + self.get_path() + "'"
+ _close = nmlib.notmuch_database_close
+ _close.argtypes = [NotmuchDatabaseP]
+ _close.restype = None
+
def __del__(self):
"""Close and free the notmuch database if needed"""
if self._db is not None:
- nmlib.notmuch_database_close(self._db)
+ self._close(self._db)
def _get_user_default_db(self):
""" Reads a user's notmuch config and returns his db location
@@ -545,18 +588,22 @@ class Query(object):
"""notmuch_query_create"""
_create = nmlib.notmuch_query_create
- _create.restype = c_void_p
+ _create.argtypes = [NotmuchDatabaseP, c_char_p]
+ _create.restype = NotmuchQueryP
"""notmuch_query_search_threads"""
_search_threads = nmlib.notmuch_query_search_threads
- _search_threads.restype = c_void_p
+ _search_threads.argtypes = [NotmuchQueryP]
+ _search_threads.restype = NotmuchThreadsP
"""notmuch_query_search_messages"""
_search_messages = nmlib.notmuch_query_search_messages
- _search_messages.restype = c_void_p
+ _search_messages.argtypes = [NotmuchQueryP]
+ _search_messages.restype = NotmuchMessagesP
"""notmuch_query_count_messages"""
_count_messages = nmlib.notmuch_query_count_messages
+ _count_messages.argtypes = [NotmuchQueryP]
_count_messages.restype = c_uint
def __init__(self, db, querystr):
@@ -602,6 +649,10 @@ class Query(object):
raise NullPointerError
self._query = query_p
+ _set_sort = nmlib.notmuch_query_set_sort
+ _set_sort.argtypes = [NotmuchQueryP, c_uint]
+ _set_sort.argtypes = None
+
def set_sort(self, sort):
"""Set the sort order future results will be delivered in
@@ -609,7 +660,7 @@ class Query(object):
"""
self._assert_query_is_initialized()
self.sort = sort
- nmlib.notmuch_query_set_sort(self._query, sort)
+ self._set_sort(self._query, sort)
def search_threads(self):
"""Execute a query for threads
@@ -661,10 +712,14 @@ class Query(object):
self._assert_query_is_initialized()
return Query._count_messages(self._query)
+ _destroy = nmlib.notmuch_query_destroy
+ _destroy.argtypes = [NotmuchQueryP]
+ _destroy.restype = None
+
def __del__(self):
"""Close and free the Query"""
if self._query is not None:
- nmlib.notmuch_query_destroy(self._query)
+ self._destroy(self._query)
class Directory(object):
@@ -683,22 +738,27 @@ class Directory(object):
"""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 = [c_char_p, c_long]
+ _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.restype = c_void_p
+ _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.restype = c_void_p
+ _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"""
+ """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
+ if dir_p is None"""
if self._dir_p is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
@@ -735,10 +795,12 @@ class Directory(object):
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.
+ .. 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
:returns: Nothing on success, raising an exception on failure.
@@ -815,10 +877,14 @@ class Directory(object):
"""Object representation"""
return "<notmuch Directory object '%s'>" % self._path
+ _destroy = nmlib.notmuch_directory_destroy
+ _destroy.argtypes = [NotmuchDirectoryP]
+ _destroy.argtypes = None
+
def __del__(self):
"""Close and free the Directory"""
if self._dir_p is not None:
- nmlib.notmuch_directory_destroy(self._dir_p)
+ self._destroy(self._dir_p)
class Filenames(object):
@@ -826,6 +892,7 @@ class Filenames(object):
#notmuch_filenames_get
_get = nmlib.notmuch_filenames_get
+ _get.argtypes = [NotmuchFilenamesP]
_get.restype = c_char_p
def __init__(self, files_p, parent):
@@ -844,41 +911,56 @@ class Filenames(object):
""" 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 self._files_p is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- if not nmlib.notmuch_filenames_valid(self._files_p):
+ if not self._valid(self._files_p):
self._files_p = None
raise StopIteration
file = Filenames._get(self._files_p)
- nmlib.notmuch_filenames_move_to_next(self._files_p)
+ self._move_to_next(self._files_p)
return file
def __len__(self):
"""len(:class:`Filenames`) returns the number of contained files
- .. note:: As this iterates over the files, we will not be able to
- iterate over them again! So this will fail::
+ .. note::
+
+ As this iterates over the files, we will not be able to
+ iterate over them again! So this will fail::
#THIS FAILS
files = Database().get_directory('').get_child_files()
- if len(files) > 0: #this 'exhausts' msgs
- # next line raises NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)!!!
+ if len(files) > 0: # this 'exhausts' msgs
+ # next line raises
+ # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
for file in files: print file
"""
if self._files_p is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
i = 0
- while nmlib.notmuch_filenames_valid(self._files_p):
- nmlib.notmuch_filenames_move_to_next(self._files_p)
+ while self._valid(self._files_p):
+ self._move_to_next(self._files_p)
i += 1
self._files_p = None
return i
+ _destroy = nmlib.notmuch_filenames_destroy
+ _destroy.argtypes = [NotmuchFilenamesP]
+ _destroy.restype = None
+
def __del__(self):
"""Close and free Filenames"""
if self._files_p is not None:
- nmlib.notmuch_filenames_destroy(self._files_p)
+ self._destroy(self._files_p)
diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py
index de4d785a..a7cd7e63 100644
--- a/bindings/python/notmuch/filename.py
+++ b/bindings/python/notmuch/filename.py
@@ -17,7 +17,8 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
"""
from ctypes import c_char_p
-from notmuch.globals import nmlib, STATUS, NotmuchError
+from notmuch.globals import (nmlib, STATUS, NotmuchError,
+ NotmuchFilenamesP, NotmuchMessageP)
class Filenames(object):
@@ -50,6 +51,7 @@ class Filenames(object):
#notmuch_filenames_get
_get = nmlib.notmuch_filenames_get
+ _get.argtypes = [NotmuchFilenamesP]
_get.restype = c_char_p
def __init__(self, files_p, parent):
@@ -74,6 +76,14 @@ class Filenames(object):
#save reference to parent object so we keep it alive
self._parent = parent
+ _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 as_generator(self):
"""Return generator of Filenames
@@ -82,13 +92,16 @@ class Filenames(object):
if self._files is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- while nmlib.notmuch_filenames_valid(self._files):
+ while self._valid(self._files):
yield Filenames._get(self._files)
- nmlib.notmuch_filenames_move_to_next(self._files)
+ self._move_to_next(self._files)
self._files = None
def __str__(self):
+ return unicode(self).encode('utf-8')
+
+ def __unicode__(self):
"""Represent Filenames() as newline-separated list of full paths
.. note:: As this iterates over the filenames, we will not be
@@ -101,7 +114,11 @@ class Filenames(object):
"""
return "\n".join(self)
+ _destroy = nmlib.notmuch_filenames_destroy
+ _destroy.argtypes = [NotmuchMessageP]
+ _destroy.restype = None
+
def __del__(self):
"""Close and free the notmuch filenames"""
if self._files is not None:
- nmlib.notmuch_filenames_destroy(self._files)
+ self._destroy(self._files)
diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py
index de1db161..54a49b2d 100644
--- a/bindings/python/notmuch/globals.py
+++ b/bindings/python/notmuch/globals.py
@@ -17,8 +17,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
"""
-from ctypes import CDLL, c_char_p, c_int
-from ctypes.util import find_library
+from ctypes import CDLL, c_char_p, c_int, Structure, POINTER
#-----------------------------------------------------------------------------
#package-global instance of the notmuch library
@@ -49,11 +48,11 @@ class Status(Enum):
@classmethod
def status2str(self, status):
- """Get a string representation of a notmuch_status_t value."""
+ """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 str(Status._status2str(status))
+ return u"Operation on uninitialized object impossible."
+ return unicode(Status._status2str(status))
STATUS = Status(['SUCCESS',
'OUT_OF_MEMORY',
@@ -89,6 +88,7 @@ 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):
"""Is initiated with a (notmuch.STATUS[, message=None]). It will not
return an instance of the class NotmuchError, but a derived instance
@@ -97,7 +97,8 @@ class NotmuchError(Exception):
@classmethod
def get_exc_subclass(cls, status):
- """Returns a fine grained Exception() type,detailing the error status"""
+ """Returns a fine grained Exception() type,
+ detailing the error status"""
subclasses = {
STATUS.OUT_OF_MEMORY: OutOfMemoryError,
STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
@@ -109,7 +110,7 @@ class NotmuchError(Exception):
STATUS.TAG_TOO_LONG: TagTooLongError,
STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
- STATUS.NOT_INITIALIZED: NotInitializedError
+ STATUS.NOT_INITIALIZED: NotInitializedError,
}
assert 0 < status <= len(subclasses)
return subclasses[status]
@@ -125,7 +126,7 @@ class NotmuchError(Exception):
# 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?
+ subclass = cls.get_exc_subclass(status) # which class to use?
return subclass.__new__(subclass, *args, **kwargs)
def __init__(self, status=None, message=None):
@@ -133,35 +134,59 @@ class NotmuchError(Exception):
self.message = message
def __str__(self):
+ return unicode(self).encode('utf-8')
+
+ 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'
+ return u'Unknown error'
+
# List of Subclassed exceptions that correspond to STATUS values and are
# subclasses of NotmuchError.
class OutOfMemoryError(NotmuchError):
status = STATUS.OUT_OF_MEMORY
+
+
class ReadOnlyDatabaseError(NotmuchError):
status = STATUS.READ_ONLY_DATABASE
+
+
class XapianError(NotmuchError):
status = STATUS.XAPIAN_EXCEPTION
+
+
class FileError(NotmuchError):
status = STATUS.FILE_ERROR
+
+
class FileNotEmailError(NotmuchError):
status = STATUS.FILE_NOT_EMAIL
+
+
class DuplicateMessageIdError(NotmuchError):
status = STATUS.DUPLICATE_MESSAGE_ID
+
+
class NullPointerError(NotmuchError):
status = STATUS.NULL_POINTER
+
+
class TagTooLongError(NotmuchError):
status = STATUS.TAG_TOO_LONG
+
+
class UnbalancedFreezeThawError(NotmuchError):
status = STATUS.UNBALANCED_FREEZE_THAW
+
+
class UnbalancedAtomicError(NotmuchError):
status = STATUS.UNBALANCED_ATOMIC
+
+
class NotInitializedError(NotmuchError):
"""Derived from NotmuchError, this occurs if the underlying data
structure (e.g. database is not initialized (yet) or an iterator has
@@ -170,7 +195,6 @@ class NotInitializedError(NotmuchError):
status = STATUS.NOT_INITIALIZED
-
def _str(value):
"""Ensure a nicely utf-8 encoded string to pass to libnotmuch
@@ -182,3 +206,47 @@ def _str(value):
return value.encode('UTF-8')
return value
+
+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 NotmuchTagsS(Structure):
+ pass
+NotmuchTagsP = POINTER(NotmuchTagsS)
+
+
+class NotmuchDirectoryS(Structure):
+ pass
+NotmuchDirectoryP = POINTER(NotmuchDirectoryS)
+
+
+class NotmuchFilenamesS(Structure):
+ pass
+NotmuchFilenamesP = POINTER(NotmuchFilenamesS)
diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py
index 4bf90c22..ce8e7181 100644
--- a/bindings/python/notmuch/message.py
+++ b/bindings/python/notmuch/message.py
@@ -19,14 +19,14 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
"""
-from ctypes import c_char_p, c_void_p, c_long, c_uint, c_int
+from ctypes import c_char_p, c_long, c_uint, c_int
from datetime import date
-from notmuch.globals import nmlib, STATUS, NotmuchError, Enum, _str
+from notmuch.globals import (nmlib, STATUS, NotmuchError, Enum, _str,
+ NotmuchTagsP, NotmuchMessagesP, NotmuchMessageP, NotmuchFilenamesP)
from notmuch.tag import Tags
from notmuch.filename import Filenames
import sys
import email
-import types
try:
import simplejson as json
except ImportError:
@@ -92,10 +92,12 @@ class Messages(object):
#notmuch_messages_get
_get = nmlib.notmuch_messages_get
- _get.restype = c_void_p
+ _get.argtypes = [NotmuchMessagesP]
+ _get.restype = NotmuchMessageP
_collect_tags = nmlib.notmuch_messages_collect_tags
- _collect_tags.restype = c_void_p
+ _collect_tags.argtypes = [NotmuchMessagesP]
+ _collect_tags.restype = NotmuchTagsP
def __init__(self, msgs_p, parent=None):
"""
@@ -125,10 +127,12 @@ class Messages(object):
"""Return the unique :class:`Tags` in the contained messages
:returns: :class:`Tags`
- :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
+ :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not init'ed
- .. note:: :meth:`collect_tags` will iterate over the messages and
- therefore will not allow further iterations.
+ .. note::
+
+ :meth:`collect_tags` will iterate over the messages and therefore
+ will not allow further iterations.
"""
if self._msgs is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
@@ -146,16 +150,24 @@ class Messages(object):
""" 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 self._msgs is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- if not nmlib.notmuch_messages_valid(self._msgs):
+ if not self._valid(self._msgs):
self._msgs = None
raise StopIteration
msg = Message(Messages._get(self._msgs), self)
- nmlib.notmuch_messages_move_to_next(self._msgs)
+ self._move_to_next(self._msgs)
return msg
def __nonzero__(self):
@@ -163,12 +175,16 @@ class Messages(object):
:return: True if there is at least one more thread in the
Iterator, False if not."""
return self._msgs is not None and \
- nmlib.notmuch_messages_valid(self._msgs) > 0
+ self._valid(self._msgs) > 0
+
+ _destroy = nmlib.notmuch_messages_destroy
+ _destroy.argtypes = [NotmuchMessagesP]
+ _destroy.restype = None
def __del__(self):
"""Close and free the notmuch Messages"""
if self._msgs is not None:
- nmlib.notmuch_messages_destroy(self._msgs)
+ self._destroy(self._msgs)
def print_messages(self, format, indent=0, entire_thread=False):
"""Outputs messages as needed for 'notmuch show' to sys.stdout
@@ -235,44 +251,60 @@ class Message(object):
"""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.restype = c_void_p
+ _get_filenames.argtypes = [NotmuchMessageP]
+ _get_filenames.restype = NotmuchFilenamesP
"""notmuch_message_get_flag"""
_get_flag = nmlib.notmuch_message_get_flag
- _get_flag.restype = c_uint
+ _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.restype = c_void_p
+ _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.restype = c_void_p
+ _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
#Constants: Flags that can be set/get with set_flag
@@ -328,14 +360,15 @@ class Message(object):
"""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 `None`.
+ .. 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
+ `None`.
:returns: :class:`Messages` or `None` if there are no replies to
this message.
@@ -394,7 +427,7 @@ class Message(object):
header = Message._get_header(self._msg, header)
if header == None:
raise NotmuchError(STATUS.NULL_POINTER)
- return header.decode('UTF-8')
+ return header.decode('UTF-8', errors='ignore')
def get_filename(self):
"""Returns the file path of the message file
@@ -450,7 +483,7 @@ class Message(object):
"""
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- nmlib.notmuch_message_set_flag(self._msg, flag, value)
+ self._set_flag(self._msg, flag, value)
def get_tags(self):
"""Returns the message tags
@@ -470,6 +503,10 @@ class Message(object):
raise NotmuchError(STATUS.NULL_POINTER)
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
@@ -504,7 +541,7 @@ class Message(object):
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = nmlib.notmuch_message_add_tag(self._msg, _str(tag))
+ status = self._add_tag(self._msg, _str(tag))
# bail out on failure
if status != STATUS.SUCCESS:
@@ -514,6 +551,10 @@ class Message(object):
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
@@ -548,7 +589,7 @@ class Message(object):
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = nmlib.notmuch_message_remove_tag(self._msg, _str(tag))
+ status = self._remove_tag(self._msg, _str(tag))
# bail out on error
if status != STATUS.SUCCESS:
raise NotmuchError(status)
@@ -557,6 +598,10 @@ class Message(object):
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.
@@ -585,7 +630,7 @@ class Message(object):
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = nmlib.notmuch_message_remove_all_tags(self._msg)
+ status = self._remove_all_tags(self._msg)
# bail out on error
if status != STATUS.SUCCESS:
@@ -595,12 +640,16 @@ class Message(object):
self.tags_to_maildir_flags()
return STATUS.SUCCESS
+ _freeze = nmlib.notmuch_message_freeze
+ _freeze.argtypes = [NotmuchMessageP]
+ _freeze.restype = c_uint
+
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.
+ 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
@@ -639,7 +688,7 @@ class Message(object):
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = nmlib.notmuch_message_freeze(self._msg)
+ status = self._freeze(self._msg)
if STATUS.SUCCESS == status:
# return on success
@@ -647,6 +696,10 @@ class Message(object):
raise NotmuchError(status)
+ _thaw = nmlib.notmuch_message_thaw
+ _thaw.argtypes = [NotmuchMessageP]
+ _thaw.restype = c_uint
+
def thaw(self):
"""Thaws the current 'message'
@@ -674,7 +727,7 @@ class Message(object):
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = nmlib.notmuch_message_thaw(self._msg)
+ status = self._thaw(self._msg)
if STATUS.SUCCESS == status:
# return on success
@@ -705,11 +758,11 @@ class Message(object):
not work yet, as the modified tags have not been committed yet
to the database.
- :returns: a :class:`STATUS`. In short, you want to see
+ :returns: a :class:`STATUS` value. In short, you want to see
notmuch.STATUS.SUCCESS here. See there for details."""
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = Message._tags_to_maildir_flags(self._msg)
+ return Message._tags_to_maildir_flags(self._msg)
def maildir_flags_to_tags(self):
"""Synchronize file Maildir flags to notmuch tags
@@ -736,19 +789,21 @@ class Message(object):
notmuch.STATUS.SUCCESS here. See there for details."""
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- status = Message._tags_to_maildir_flags(self._msg)
+ return Message._tags_to_maildir_flags(self._msg)
def __repr__(self):
"""Represent a Message() object by str()"""
return self.__str__()
def __str__(self):
- """A message() is represented by a 1-line summary"""
- msg = {}
- msg['from'] = self.get_header('from')
- msg['tags'] = self.get_tags()
- msg['date'] = date.fromtimestamp(self.get_date())
- return "%(from)s (%(date)s) (%(tags)s)" % (msg)
+ return unicode(self).encode('utf-8')
+
+ 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"""
@@ -896,7 +951,11 @@ class Message(object):
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 is not None:
- nmlib.notmuch_message_destroy(self._msg)
+ self._destroy(self._msg)
diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py
index 50e3686b..2fb7d328 100644
--- a/bindings/python/notmuch/tag.py
+++ b/bindings/python/notmuch/tag.py
@@ -17,7 +17,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
"""
from ctypes import c_char_p
-from notmuch.globals import nmlib, STATUS, NotmuchError
+from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP
class Tags(object):
@@ -50,6 +50,7 @@ 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):
@@ -80,14 +81,22 @@ class Tags(object):
""" 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 self._tags is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- if not nmlib.notmuch_tags_valid(self._tags):
+ if not self._valid(self._tags):
self._tags = None
raise StopIteration
tag = Tags._get(self._tags).decode('UTF-8')
- nmlib.notmuch_tags_move_to_next(self._tags)
+ self._move_to_next(self._tags)
return tag
def __nonzero__(self):
@@ -99,20 +108,28 @@ class Tags(object):
:returns: True if the Tags() iterator has at least one more Tag
left."""
- return nmlib.notmuch_tags_valid(self._tags) > 0
+ return self._valid(self._tags) > 0
def __str__(self):
- """The str() representation of Tags() is a space separated list of tags
+ return unicode(self).encode('utf-8')
+
+ 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:`NotmuchError` STATUS.NOT_INITIALIZED on
- subsequent attempts.
+ .. 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:`NotmuchError`
+ STATUS.NOT_INITIALIZED 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 is not None:
- nmlib.notmuch_tags_destroy(self._tags)
+ self._destroy(self._tags)
diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py
index 5e08eb31..5058846d 100644
--- a/bindings/python/notmuch/thread.py
+++ b/bindings/python/notmuch/thread.py
@@ -17,8 +17,10 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
"""
-from ctypes import c_char_p, c_void_p, c_long
-from notmuch.globals import nmlib, STATUS, NotmuchError
+from ctypes import c_char_p, c_long, c_int
+from notmuch.globals import (nmlib, STATUS,
+ NotmuchError, NotmuchThreadP, NotmuchThreadsP, NotmuchMessagesP,
+ NotmuchTagsP,)
from notmuch.message import Messages
from notmuch.tag import Tags
from datetime import date
@@ -75,7 +77,8 @@ class Threads(object):
#notmuch_threads_get
_get = nmlib.notmuch_threads_get
- _get.restype = c_void_p
+ _get.argtypes = [NotmuchThreadsP]
+ _get.restype = NotmuchThreadP
def __init__(self, threads_p, parent=None):
"""
@@ -105,16 +108,24 @@ class Threads(object):
""" 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 self._threads is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- if not nmlib.notmuch_threads_valid(self._threads):
+ if not self._valid(self._threads):
self._threads = None
raise StopIteration
thread = Thread(Threads._get(self._threads), self)
- nmlib.notmuch_threads_move_to_next(self._threads)
+ self._move_to_next(self._threads)
return thread
def __len__(self):
@@ -134,8 +145,8 @@ class Threads(object):
i = 0
# returns 'bool'. On out-of-memory it returns None
- while nmlib.notmuch_threads_valid(self._threads):
- nmlib.notmuch_threads_move_to_next(self._threads)
+ while self._valid(self._threads):
+ self._move_to_next(self._threads)
i += 1
# reset self._threads to mark as "exhausted"
self._threads = None
@@ -153,12 +164,16 @@ class Threads(object):
Iterator, False if not. None on a "Out-of-memory" error.
"""
return self._threads is not None and \
- nmlib.notmuch_threads_valid(self._threads) > 0
+ self._valid(self._threads) > 0
+
+ _destroy = nmlib.notmuch_threads_destroy
+ _destroy.argtypes = [NotmuchThreadsP]
+ _destroy.argtypes = None
def __del__(self):
"""Close and free the notmuch Threads"""
if self._threads is not None:
- nmlib.notmuch_messages_destroy(self._threads)
+ self._destroy(self._threads)
class Thread(object):
@@ -166,29 +181,36 @@ class Thread(object):
"""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.restype = c_void_p
+ _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.restype = c_void_p
+ _get_tags.argtypes = [NotmuchThreadP]
+ _get_tags.restype = NotmuchTagsP
def __init__(self, thread_p, parent=None):
"""
@@ -225,6 +247,10 @@ class Thread(object):
raise NotmuchError(STATUS.NOT_INITIALIZED)
return Thread._get_thread_id(self._thread)
+ _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'
@@ -236,7 +262,7 @@ class Thread(object):
"""
if self._thread is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- return nmlib.notmuch_thread_get_total_messages(self._thread)
+ return self._get_total_messages(self._thread)
def get_toplevel_messages(self):
"""Returns a :class:`Messages` iterator for the top-level messages in
@@ -267,6 +293,10 @@ class Thread(object):
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
@@ -278,7 +308,7 @@ class Thread(object):
"""
if self._thread is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
- return nmlib.notmuch_thread_get_matched_messages(self._thread)
+ return self._get_matched_messages(self._thread)
def get_authors(self):
"""Returns the authors of 'thread'
@@ -295,7 +325,7 @@ class Thread(object):
authors = Thread._get_authors(self._thread)
if authors is None:
return None
- return authors.decode('UTF-8')
+ return authors.decode('UTF-8', errors='ignore')
def get_subject(self):
"""Returns the Subject of 'thread'
@@ -308,7 +338,7 @@ class Thread(object):
subject = Thread._get_subject(self._thread)
if subject is None:
return None
- return subject.decode('UTF-8')
+ return subject.decode('UTF-8', errors='ignore')
def get_newest_date(self):
"""Returns time_t of the newest message date
@@ -362,32 +392,25 @@ class Thread(object):
return Tags(tags_p, self)
def __str__(self):
- """A str(Thread()) is represented by a 1-line summary"""
- thread = {}
- thread['id'] = self.get_thread_id()
+ return unicode(self).encode('utf-8')
+
+ def __unicode__(self):
+ frm = "thread:%s %12s [%d/%d] %s; %s (%s)"
- ###TODO: How do we find out the current sort order of Threads?
- ###Add a "sort" attribute to the Threads() object?
- #if (sort == NOTMUCH_SORT_OLDEST_FIRST)
- # date = notmuch_thread_get_oldest_date (thread);
- #else
- # date = notmuch_thread_get_newest_date (thread);
- thread['date'] = date.fromtimestamp(self.get_newest_date())
- thread['matched'] = self.get_matched_messages()
- thread['total'] = self.get_total_messages()
- thread['authors'] = self.get_authors()
- thread['subject'] = self.get_subject()
- thread['tags'] = self.get_tags()
+ 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(),
+ )
- return "thread:%s %12s [%d/%d] %s; %s (%s)" % (thread['id'],
- thread['date'],
- thread['matched'],
- thread['total'],
- thread['authors'],
- thread['subject'],
- thread['tags'])
+ _destroy = nmlib.notmuch_thread_destroy
+ _destroy.argtypes = [NotmuchThreadP]
+ _destroy.restype = None
def __del__(self):
"""Close and free the notmuch Thread"""
if self._thread is not None:
- nmlib.notmuch_thread_destroy(self._thread)
+ self._destroy(self._thread)
diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py
index fd414b0a..59c396fe 100644
--- a/bindings/python/notmuch/version.py
+++ b/bindings/python/notmuch/version.py
@@ -1,2 +1,2 @@
# this file should be kept in sync with ../../../version
-__VERSION__ = '0.10.2'
+__VERSION__ = '0.11'
diff --git a/command-line-arguments.c b/command-line-arguments.c
new file mode 100644
index 00000000..e7114143
--- /dev/null
+++ b/command-line-arguments.c
@@ -0,0 +1,155 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include "error_util.h"
+#include "command-line-arguments.h"
+
+/*
+ Search the array of keywords for a given argument, assigning the
+ output variable to the corresponding value. Return FALSE if nothing
+ matches.
+*/
+
+static notmuch_bool_t
+_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, const char *arg_str) {
+
+ const notmuch_keyword_t *keywords = arg_desc->keywords;
+
+ while (keywords->name) {
+ if (strcmp (arg_str, keywords->name) == 0) {
+ if (arg_desc->output_var) {
+ *((int *)arg_desc->output_var) = keywords->value;
+ }
+ return TRUE;
+ }
+ keywords++;
+ }
+ fprintf (stderr, "unknown keyword: %s\n", arg_str);
+ return FALSE;
+}
+
+/*
+ Search for the {pos_arg_index}th position argument, return FALSE if
+ that does not exist.
+*/
+
+notmuch_bool_t
+parse_position_arg (const char *arg_str, int pos_arg_index,
+ const notmuch_opt_desc_t *arg_desc) {
+
+ int pos_arg_counter = 0;
+ while (arg_desc->opt_type != NOTMUCH_OPT_END){
+ if (arg_desc->opt_type == NOTMUCH_OPT_POSITION) {
+ if (pos_arg_counter == pos_arg_index) {
+ if (arg_desc->output_var) {
+ *((const char **)arg_desc->output_var) = arg_str;
+ }
+ return TRUE;
+ }
+ pos_arg_counter++;
+ }
+ arg_desc++;
+ }
+ return FALSE;
+}
+
+/*
+ * Search for a non-positional (i.e. starting with --) argument matching arg,
+ * parse a possible value, and assign to *output_var
+ */
+
+notmuch_bool_t
+parse_option (const char *arg,
+ const notmuch_opt_desc_t *options) {
+
+ assert(arg);
+ assert(options);
+
+ arg += 2;
+
+ const notmuch_opt_desc_t *try = options;
+ while (try->opt_type != NOTMUCH_OPT_END) {
+ if (try->name && strncmp (arg, try->name, strlen (try->name)) == 0) {
+ char next = arg[strlen (try->name)];
+ const char *value= arg+strlen(try->name)+1;
+
+ char *endptr;
+
+ /* Everything but boolean arguments (switches) needs a
+ * delimiter, and a non-zero length value
+ */
+
+ if (try->opt_type != NOTMUCH_OPT_BOOLEAN) {
+ if (next != '=' && next != ':') return FALSE;
+ if (value[0] == 0) return FALSE;
+ } else {
+ if (next != 0) return FALSE;
+ }
+
+ if (try->output_var == NULL)
+ INTERNAL_ERROR ("output pointer NULL for option %s", try->name);
+
+ switch (try->opt_type) {
+ case NOTMUCH_OPT_KEYWORD:
+ return _process_keyword_arg (try, value);
+ break;
+ case NOTMUCH_OPT_BOOLEAN:
+ *((notmuch_bool_t *)try->output_var) = TRUE;
+ return TRUE;
+ break;
+ case NOTMUCH_OPT_INT:
+ *((int *)try->output_var) = strtol (value, &endptr, 10);
+ return (*endptr == 0);
+ break;
+ case NOTMUCH_OPT_STRING:
+ *((const char **)try->output_var) = value;
+ return TRUE;
+ break;
+ case NOTMUCH_OPT_POSITION:
+ case NOTMUCH_OPT_END:
+ default:
+ INTERNAL_ERROR ("unknown or unhandled option type %d", try->opt_type);
+ /*UNREACHED*/
+ }
+ }
+ try++;
+ }
+ fprintf (stderr, "Unrecognized option: --%s\n", arg);
+ return FALSE;
+}
+
+/* See command-line-arguments.h for description */
+int
+parse_arguments (int argc, char **argv,
+ const notmuch_opt_desc_t *options, int opt_index) {
+
+ int pos_arg_index = 0;
+ notmuch_bool_t more_args = TRUE;
+
+ while (more_args && opt_index < argc) {
+ if (strncmp (argv[opt_index],"--",2) != 0) {
+
+ more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
+
+ if (more_args) {
+ pos_arg_index++;
+ opt_index++;
+ }
+
+ } else {
+
+ if (strlen (argv[opt_index]) == 2)
+ return opt_index+1;
+
+ more_args = parse_option (argv[opt_index], options);
+ if (more_args) {
+ opt_index++;
+ } else {
+ opt_index = -1;
+ }
+
+ }
+ }
+
+ return opt_index;
+}
diff --git a/command-line-arguments.h b/command-line-arguments.h
new file mode 100644
index 00000000..de1734ad
--- /dev/null
+++ b/command-line-arguments.h
@@ -0,0 +1,80 @@
+#ifndef NOTMUCH_OPTS_H
+#define NOTMUCH_OPTS_H
+
+#include "notmuch.h"
+
+enum notmuch_opt_type {
+ NOTMUCH_OPT_END = 0,
+ NOTMUCH_OPT_BOOLEAN, /* --verbose */
+ NOTMUCH_OPT_INT, /* --frob=8 */
+ NOTMUCH_OPT_KEYWORD, /* --format=raw|json|text */
+ NOTMUCH_OPT_STRING, /* --file=/tmp/gnarf.txt */
+ NOTMUCH_OPT_POSITION /* notmuch dump pos_arg */
+};
+
+/*
+ * Describe one of the possibilities for a keyword option
+ * 'value' will be copied to the output variable
+ */
+
+typedef struct notmuch_keyword {
+ const char *name;
+ int value;
+} notmuch_keyword_t;
+
+/*
+ * Describe one option.
+ *
+ * First two parameters are mandatory.
+ *
+ * name is mandatory _except_ for positional arguments.
+ *
+ * arg_id is currently unused, but could define short arguments.
+ *
+ * keywords is a (possibly NULL) pointer to an array of keywords
+ */
+typedef struct notmuch_opt_desc {
+ enum notmuch_opt_type opt_type;
+ void *output_var;
+ const char *name;
+ int arg_id;
+ const struct notmuch_keyword *keywords;
+} notmuch_opt_desc_t;
+
+
+/*
+ This is the main entry point for command line argument parsing.
+
+ Parse command line arguments according to structure options,
+ starting at position opt_index.
+
+ All output of parsed values is via pointers in options.
+
+ Parsing stops at -- (consumed) or at the (k+1)st argument
+ not starting with -- (a "positional argument") if options contains
+ k positional argument descriptors.
+
+ Returns the index of first non-parsed argument, or -1 in case of error.
+
+*/
+int
+parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index);
+
+/*
+ * If the argument parsing loop provided by parse_arguments is not
+ * flexible enough, then the user might be interested in the following
+ * routines, but note that the API to parse_option might have to
+ * change. See command-line-arguments.c for descriptions of these
+ * functions.
+ */
+
+notmuch_bool_t
+parse_option (const char *arg, const notmuch_opt_desc_t* options);
+
+notmuch_bool_t
+parse_position_arg (const char *arg,
+ int position_arg_index,
+ const notmuch_opt_desc_t* options);
+
+
+#endif
diff --git a/contrib/.gitattributes b/contrib/.gitattributes
deleted file mode 100644
index 3d57a081..00000000
--- a/contrib/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-notmuch-deliver export-ignore
diff --git a/contrib/notmuch-deliver/COPYING b/contrib/notmuch-deliver/COPYING
deleted file mode 100644
index 3912109b..00000000
--- a/contrib/notmuch-deliver/COPYING
+++ /dev/null
@@ -1,340 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) 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
-this service 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 make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. 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.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the 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 a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE 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.
-
- 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
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the 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 2 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, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision 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, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This 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 Library General
-Public License instead of this License.
diff --git a/contrib/notmuch-deliver/README.mkd b/contrib/notmuch-deliver/README.mkd
index f1f2144f..06268bd8 100644
--- a/contrib/notmuch-deliver/README.mkd
+++ b/contrib/notmuch-deliver/README.mkd
@@ -38,12 +38,6 @@ My personal e-mail address is [alip@exherbo.org](mailto:alip@exherbo.org).
I'm available on IRC as `alip` on [Freenode](http://freenode.net) and [OFTC](http://www.oftc.net).
## License
-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, version 2 of the License.
-
-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 may redistribute this under the same terms as notmuch itself.
<!-- vim: set ft=mkd spell spelllang=en sw=4 sts=4 et : -->
diff --git a/contrib/notmuch-deliver/maildrop/maildir/Makefile.am b/contrib/notmuch-deliver/maildrop/maildir/Makefile.am
index 2495a83b..6fd0ae49 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/Makefile.am
+++ b/contrib/notmuch-deliver/maildrop/maildir/Makefile.am
@@ -1,4 +1,3 @@
-#$Id: Makefile.am,v 1.59 2009/06/27 16:32:38 mrsam Exp $
#
# Copyright 1998 - 2005 Double Precision, Inc. See COPYING for
# distribution information.
diff --git a/contrib/notmuch-deliver/maildrop/maildir/configure.in b/contrib/notmuch-deliver/maildrop/maildir/configure.in
index 1e724443..dfdd22a3 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/configure.in
+++ b/contrib/notmuch-deliver/maildrop/maildir/configure.in
@@ -1,4 +1,3 @@
-dnl $Id: configure.in,v 1.37 2008/11/27 21:51:16 mrsam Exp $
dnl Process this file with autoconf to produce a configure script.
dnl
dnl Copyright 1998 - 2001 Double Precision, Inc. See COPYING for
@@ -67,6 +66,7 @@ AC_TYPE_OFF_T
AC_TYPE_SIZE_T
AC_TYPE_UID_T
AC_STRUCT_TM
+AC_SYS_LARGEFILE
dnl Checks for library functions.
AC_CHECK_HEADER(fam.h, :, :)
diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c
index 74030f41..5efc0afb 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c
+++ b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c
@@ -35,7 +35,6 @@
#include "numlib/numlib.h"
-static const char rcsid[]="$Id: maildircreate.c,v 1.6 2003/01/26 04:07:03 mrsam Exp $";
FILE *maildir_tmpcreate_fp(struct maildir_tmpcreate_info *info)
{
diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h
index ea1c71ac..85391454 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h
+++ b/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h
@@ -16,7 +16,6 @@
extern "C" {
#endif
-static const char maildircreate_h_rcsid[]="$Id: maildircreate.h,v 1.10 2006/10/29 00:03:53 mrsam Exp $";
/* Create messages in maildirs */
diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h b/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h
index 545d11e1..e1e7c19a 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h
+++ b/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h
@@ -18,7 +18,6 @@
extern "C" {
#endif
-static const char maildirmisc_h_rcsid[]="$Id: maildirmisc.h,v 1.18 2006/07/22 02:48:15 mrsam Exp $";
/*
**
diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c b/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c
index 754b2c70..28a3ac21 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c
+++ b/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c
@@ -18,7 +18,6 @@
#include "maildirmisc.h"
-static const char rcsid[]="$Id: maildirmkdir.c,v 1.2 2002/03/15 03:09:21 mrsam Exp $";
int maildir_mkdir(const char *dir)
{
diff --git a/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c b/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c
index 5071df76..25428737 100644
--- a/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c
+++ b/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c
@@ -22,7 +22,6 @@
#include "maildirmisc.h"
-static const char rcsid[]="$Id: maildiropen.c,v 1.8 2003/01/19 16:39:52 mrsam Exp $";
char *maildir_getlink(const char *filename)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/Makefile.am b/contrib/notmuch-deliver/maildrop/numlib/Makefile.am
index c0f129fa..0a5f103d 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/Makefile.am
+++ b/contrib/notmuch-deliver/maildrop/numlib/Makefile.am
@@ -1,4 +1,3 @@
-# $Id: Makefile.am,v 1.12 2007/06/30 15:40:53 mrsam Exp $
#
# Copyright 1998 - 2004 Double Precision, Inc. See COPYING for
# distribution information.
diff --git a/contrib/notmuch-deliver/maildrop/numlib/atotimet.c b/contrib/notmuch-deliver/maildrop/numlib/atotimet.c
index d494fd22..0360b5b2 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/atotimet.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/atotimet.c
@@ -9,6 +9,5 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: atotimet.c,v 1.1 2003/08/03 03:09:19 mrsam Exp $";
LIBMAIL_STRIMPL(time_t, libmail_strtotime_t, libmail_atotime_t)
diff --git a/contrib/notmuch-deliver/maildrop/numlib/atouidt.c b/contrib/notmuch-deliver/maildrop/numlib/atouidt.c
index 3c01ecf5..6f869d54 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/atouidt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/atouidt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: atouidt.c,v 1.1 2004/01/11 02:47:33 mrsam Exp $";
LIBMAIL_STRIMPL(uid_t, libmail_strtouid_t, libmail_atouid_t)
LIBMAIL_STRIMPL(gid_t, libmail_strtogid_t, libmail_atogid_t)
diff --git a/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c b/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c
index 56793927..adaee406 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c
@@ -19,7 +19,6 @@
#include "numlib.h"
-static const char rcsid[]="$Id: changeuidgid.c,v 1.2 2003/01/05 04:01:17 mrsam Exp $";
void libmail_changegroup(gid_t gid)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/configure.in b/contrib/notmuch-deliver/maildrop/numlib/configure.in
index fc977aa9..7479725b 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/configure.in
+++ b/contrib/notmuch-deliver/maildrop/numlib/configure.in
@@ -1,5 +1,4 @@
dnl Process this file with autoconf to produce a configure script.
-dnl $Id: configure.in,v 1.7 2010/03/19 01:09:26 mrsam Exp $
dnl
dnl Copyright 1998 - 2010 Double Precision, Inc. See COPYING for
dnl distribution information.
@@ -39,6 +38,7 @@ AC_CHECK_TYPE(int64_t, [ : ],
dnl Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_UID_T
AC_TYPE_PID_T
+AC_SYS_LARGEFILE
dnl Checks for library functions.
diff --git a/contrib/notmuch-deliver/maildrop/numlib/numlib.h b/contrib/notmuch-deliver/maildrop/numlib/numlib.h
index 7928aa7e..c31d9b1a 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/numlib.h
+++ b/contrib/notmuch-deliver/maildrop/numlib/numlib.h
@@ -10,7 +10,6 @@
extern "C" {
#endif
-static const char numlib_h_rcsid[]="$Id: numlib.h,v 1.11 2010/03/19 01:09:26 mrsam Exp $";
#if HAVE_CONFIG_H
#include "../numlib/config.h" /* VPATH build */
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strdevt.c b/contrib/notmuch-deliver/maildrop/numlib/strdevt.c
index 2e542d54..aa8b0282 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strdevt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strdevt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strdevt.c,v 1.1 2003/01/26 03:22:40 mrsam Exp $";
char *libmail_str_dev_t(dev_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strgidt.c b/contrib/notmuch-deliver/maildrop/numlib/strgidt.c
index 89472e3b..828a4550 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strgidt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strgidt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strgidt.c,v 1.4 2003/01/05 04:01:17 mrsam Exp $";
char *libmail_str_gid_t(gid_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c b/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c
index 98e25e53..9ff45f28 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strhdevt.c,v 1.2 2003/03/12 02:45:56 mrsam Exp $";
static const char xdigit[]="0123456789ABCDEF";
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhinot.c b/contrib/notmuch-deliver/maildrop/numlib/strhinot.c
index fa640915..c3cdc10e 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strhinot.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strhinot.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strhinot.c,v 1.5 2003/03/12 02:45:56 mrsam Exp $";
static const char xdigit[]="0123456789ABCDEF";
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c b/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c
index 2723af0b..5a440f5d 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strhpidt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strhpidt.c,v 1.5 2003/03/12 02:45:56 mrsam Exp $";
static const char xdigit[]="0123456789ABCDEF";
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c b/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c
index b86b05a2..cb9e6a9f 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strhtimet.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strhtimet.c,v 1.5 2003/03/12 02:45:56 mrsam Exp $";
static const char xdigit[]="0123456789ABCDEF";
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strinot.c b/contrib/notmuch-deliver/maildrop/numlib/strinot.c
index eb544c3c..314b2f38 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strinot.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strinot.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strinot.c,v 1.4 2003/01/05 04:01:17 mrsam Exp $";
char *libmail_str_ino_t(ino_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strofft.c b/contrib/notmuch-deliver/maildrop/numlib/strofft.c
index 3435148e..567f912b 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strofft.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strofft.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strofft.c,v 1.6 2010/03/19 01:09:26 mrsam Exp $";
char *libmail_str_off_t(off_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strpidt.c b/contrib/notmuch-deliver/maildrop/numlib/strpidt.c
index 12ee9ce1..0c29b07e 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strpidt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strpidt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strpidt.c,v 1.4 2003/01/05 04:01:17 mrsam Exp $";
char *libmail_str_pid_t(pid_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strsize.c b/contrib/notmuch-deliver/maildrop/numlib/strsize.c
index 0a7dcaa8..1c903122 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strsize.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strsize.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strsize.c,v 1.2 2003/01/05 04:01:17 mrsam Exp $";
static void cat_n(char *buf, unsigned long n)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strsizet.c b/contrib/notmuch-deliver/maildrop/numlib/strsizet.c
index d4ec92d3..fd9d1d1a 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strsizet.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strsizet.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strsizet.c,v 1.4 2003/01/05 04:01:18 mrsam Exp $";
char *libmail_str_size_t(size_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/strtimet.c b/contrib/notmuch-deliver/maildrop/numlib/strtimet.c
index be7e051b..63307f22 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/strtimet.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/strtimet.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: strtimet.c,v 1.4 2003/01/05 04:01:18 mrsam Exp $";
char *libmail_str_time_t(time_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/maildrop/numlib/struidt.c b/contrib/notmuch-deliver/maildrop/numlib/struidt.c
index 50f3f742..5843ed24 100644
--- a/contrib/notmuch-deliver/maildrop/numlib/struidt.c
+++ b/contrib/notmuch-deliver/maildrop/numlib/struidt.c
@@ -9,7 +9,6 @@
#include "numlib.h"
#include <string.h>
-static const char rcsid[]="$Id: struidt.c,v 1.4 2003/01/05 04:01:18 mrsam Exp $";
char *libmail_str_uid_t(uid_t t, char *arg)
{
diff --git a/contrib/notmuch-deliver/src/main.c b/contrib/notmuch-deliver/src/main.c
index f7a4eaa6..6f32f73d 100644
--- a/contrib/notmuch-deliver/src/main.c
+++ b/contrib/notmuch-deliver/src/main.c
@@ -1,22 +1,23 @@
-/* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */
-
-/*
- * Copyright (c) 2010 Ali Polatel <alip@exherbo.org>
+/* notmuch-deliver - If you make the user a promise... make sure you deliver it!
+ *
+ * Copyright © 2010 Ali Polatel
* Based in part upon deliverquota of maildrop which is:
* Copyright 1998 - 2009 Double Precision, Inc.
*
- * This file is part of the notmuch-deliver. notmuch-deliver is free software;
- * you can redistribute it and/or modify it under the terms of the GNU General
- * Public License version 2, as published by the Free Software Foundation.
+ * 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.
*
- * notmuch-deliver 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 http://www.gnu.org/licenses/ .
*
- * You should have received a copy of the GNU General Public License along with
- * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
- * Place, Suite 330, Boston, MA 02111-1307 USA
+ * Author: Ali Polatel <polatel@gmail.com>
*/
#ifdef HAVE_CONFIG_H
diff --git a/debian/changelog b/debian/changelog
index 4f90a134..28f56587 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,32 @@
+notmuch (0.11-1) unstable; urgency=low
+
+ * New upstream release.
+ - New 'hook' feature for notmuch-new
+ - performance and memory use improvements
+ - new add-on tool notmuch-deliver
+
+ -- David Bremner <bremner@debian.org> Fri, 13 Jan 2012 19:59:23 -0400
+
+notmuch (0.11~rc3-1) experimental; urgency=low
+
+ * New upstream release candidate
+ - Fix for uninitialized variable(s) in notmuch-reply
+
+ -- David Bremner <bremner@debian.org> Mon, 09 Jan 2012 07:07:46 -0400
+
+notmuch (0.11~rc2-1) experimental; urgency=low
+
+ * New upstream release candidate.
+ - Includes fix for one python bindings segfault.
+
+ -- David Bremner <bremner@debian.org> Mon, 02 Jan 2012 06:57:29 -0400
+
+notmuch (0.11~rc1-1) experimental; urgency=low
+
+ * New upstream release candidate.
+
+ -- David Bremner <bremner@debian.org> Sun, 25 Dec 2011 23:07:08 -0400
+
notmuch (0.10.2-1~bpo60+1) squeeze-backports; urgency=low
* Rebuild for squeeze-backports.
diff --git a/debian/control b/debian/control
index 3252f32c..f6f415ed 100644
--- a/debian/control
+++ b/debian/control
@@ -16,7 +16,7 @@ Build-Depends:
python-all (>= 2.6.6-3~),
emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~),
gdb,
- dtach
+ dtach (>= 0.8)
Standards-Version: 3.9.2
Homepage: http://notmuchmail.org/
Vcs-Git: git://notmuchmail.org/git/notmuch
diff --git a/debian/gbp.conf b/debian/gbp.conf
index dba526f6..6cb451a4 100644
--- a/debian/gbp.conf
+++ b/debian/gbp.conf
@@ -10,5 +10,8 @@ debian-branch = master
# Directory for performing the build
export-dir = ./debian-build
+# Format for upstream tags
+upstream-tag = %(version)s
+
# Format for the debian tag
-debian-tag = debian-%(version)s
+debian-tag = debian/%(version)s
diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el
index 1a7c5771..8eba7a0b 100644
--- a/emacs/notmuch-address.el
+++ b/emacs/notmuch-address.el
@@ -54,15 +54,22 @@ line."
(completion-ignore-case t)
(options (notmuch-address-options orig))
(num-options (length options))
- (chosen (if (eq num-options 1)
- (car options)
+ (chosen (cond
+ ((eq num-options 0)
+ nil)
+ ((eq num-options 1)
+ (car options))
+ (t
(completing-read (format "Address (%s matches): " num-options)
(cdr options) nil nil (car options)
- 'notmuch-address-history))))
- (when chosen
- (push chosen notmuch-address-history)
- (delete-region beg end)
- (insert chosen))))
+ 'notmuch-address-history)))))
+ (if chosen
+ (progn
+ (push chosen notmuch-address-history)
+ (delete-region beg end)
+ (insert chosen))
+ (message "No matches.")
+ (ding))))
;; Copied from `w3m-which-command'.
(defun notmuch-address-locate-command (command)
diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el
index 44fccae0..ac300987 100644
--- a/emacs/notmuch-crypto.el
+++ b/emacs/notmuch-crypto.el
@@ -37,6 +37,11 @@ mode."
:group 'notmuch
:type 'boolean)
+(defface notmuch-crypto-part-header
+ '((t (:foreground "blue")))
+ "Face used for crypto parts headers."
+ :group 'notmuch)
+
(defface notmuch-crypto-signature-good
'((t (:background "green" :foreground "black")))
"Face used for good signatures."
@@ -63,7 +68,7 @@ mode."
:group 'notmuch)
(define-button-type 'notmuch-crypto-status-button-type
- 'action '(lambda (button) (message (button-get button 'help-echo)))
+ 'action (lambda (button) (message (button-get button 'help-echo)))
'follow-link t
'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts.")
@@ -72,7 +77,7 @@ mode."
(help-msg nil)
(label "Signature not processed")
(face 'notmuch-crypto-signature-unknown)
- (button-action '(lambda (button) (message (button-get button 'help-echo)))))
+ (button-action (lambda (button) (message (button-get button 'help-echo)))))
(cond
((string= status "good")
(let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint))))
diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el
index 1a76c30a..333d4c1e 100644
--- a/emacs/notmuch-hello.el
+++ b/emacs/notmuch-hello.el
@@ -42,6 +42,26 @@
:type 'boolean
:group 'notmuch)
+(defun notmuch-sort-saved-searches (alist)
+ "Generate an alphabetically sorted saved searches alist."
+ (sort alist (lambda (a b) (string< (car a) (car b)))))
+
+(defcustom notmuch-saved-search-sort-function nil
+ "Function used to sort the saved searches for the notmuch-hello view.
+
+This variable controls how saved searches should be sorted. No
+sorting (nil) displays the saved searches in the order they are
+stored in `notmuch-saved-searches'. Sort alphabetically sorts the
+saved searches in alphabetical order. Custom sort function should
+be a function or a lambda expression that takes the saved
+searches alist as a parameter, and returns a new saved searches
+alist to be used."
+ :type '(choice (const :tag "No sorting" nil)
+ (const :tag "Sort alphabetically" notmuch-sort-saved-searches)
+ (function :tag "Custom sort function"
+ :value notmuch-sort-saved-searches))
+ :group 'notmuch)
+
(defvar notmuch-hello-indent 4
"How much to indent non-headers.")
@@ -66,8 +86,9 @@ Finally this can be a function that will be called for each tag and
should return a filter for that tag, or nil to hide the tag."
:type '(choice (const :tag "All messages" nil)
(const :tag "Unread messages" "tag:unread")
- (const :tag "Custom filter" string)
- (const :tag "Custom filter function" function))
+ (string :tag "Custom filter"
+ :value "tag:unread")
+ (function :tag "Custom filter function"))
:group 'notmuch)
(defcustom notmuch-hello-hide-tags nil
@@ -111,21 +132,34 @@ So:
(integer :tag "Number of characters")
(float :tag "Fraction of window")))
-(defcustom notmuch-decimal-separator ","
- "The string used as a decimal separator.
+(defcustom notmuch-hello-thousands-separator " "
+ "The string used as a thousands separator.
-Typically \",\" in the US and UK and \".\" in Europe."
+Typically \",\" in the US and UK and \".\" or \" \" in Europe.
+The latter is recommended in the SI/ISO 31-0 standard and by the
+International Bureau of Weights and Measures."
:group 'notmuch
:type 'string)
+(defcustom notmuch-hello-mode-hook nil
+ "Functions called after entering `notmuch-hello-mode'."
+ :group 'notmuch
+ :type 'hook)
+
+(defcustom notmuch-hello-refresh-hook nil
+ "Functions called after updating a `notmuch-hello' buffer."
+ :type 'hook
+ :group 'notmuch)
+
(defvar notmuch-hello-url "http://notmuchmail.org"
"The `notmuch' web site.")
(defvar notmuch-hello-recent-searches nil)
(defun notmuch-hello-remember-search (search)
- (if (not (member search notmuch-hello-recent-searches))
- (push search notmuch-hello-recent-searches))
+ (setq notmuch-hello-recent-searches
+ (delete search notmuch-hello-recent-searches))
+ (push search notmuch-hello-recent-searches)
(if (> (length notmuch-hello-recent-searches)
notmuch-recent-searches-max)
(setq notmuch-hello-recent-searches (butlast notmuch-hello-recent-searches))))
@@ -139,7 +173,7 @@ Typically \",\" in the US and UK and \".\" in Europe."
(apply #'concat
(number-to-string (car result))
(mapcar (lambda (elem)
- (format "%s%03d" notmuch-decimal-separator elem))
+ (format "%s%03d" notmuch-hello-thousands-separator elem))
(cdr result)))))
(defun notmuch-hello-trim (search)
@@ -168,8 +202,8 @@ Typically \",\" in the US and UK and \".\" in Europe."
collect elem))
;; Add the new one.
(customize-save-variable 'notmuch-saved-searches
- (push (cons name search)
- notmuch-saved-searches))
+ (add-to-list 'notmuch-saved-searches
+ (cons name search) t))
(message "Saved '%s' as '%s'." search name)
(notmuch-hello-update)))
@@ -311,8 +345,8 @@ should be. Returns a cons cell `(tags-per-line width)'."
(defvar notmuch-hello-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map widget-keymap)
- (define-key map "v" '(lambda () "Display the notmuch version" (interactive)
- (message "notmuch version %s" (notmuch-version))))
+ (define-key map "v" (lambda () "Display the notmuch version" (interactive)
+ (message "notmuch version %s" (notmuch-version))))
(define-key map "?" 'notmuch-help)
(define-key map "q" 'notmuch-kill-this-buffer)
(define-key map "=" 'notmuch-hello-update)
@@ -335,6 +369,7 @@ Complete list of currently available key bindings:
(use-local-map notmuch-hello-mode-map)
(setq major-mode 'notmuch-hello-mode
mode-name "notmuch-hello")
+ (run-mode-hooks 'notmuch-hello-mode-hook)
;;(setq buffer-read-only t)
)
@@ -377,11 +412,16 @@ Complete list of currently available key bindings:
(progn
(widget-forward 1)
(widget-value (widget-at)))
- (error nil)))))
+ (error nil))))
+ (inhibit-read-only t))
- (kill-all-local-variables)
- (let ((inhibit-read-only t))
- (erase-buffer))
+ ;; Delete all editable widget fields. Editable widget fields are
+ ;; tracked in a buffer local variable `widget-field-list' (and
+ ;; others). If we do `erase-buffer' without properly deleting the
+ ;; widgets, some widget-related functions are confused later.
+ (mapc 'widget-delete widget-field-list)
+
+ (erase-buffer)
(unless (eq major-mode 'notmuch-hello-mode)
(notmuch-hello-mode))
@@ -440,6 +480,10 @@ Complete list of currently available key bindings:
(widest (max saved-widest alltags-widest)))
(when saved-alist
+ ;; Sort saved searches if required.
+ (when notmuch-saved-search-sort-function
+ (setq saved-alist
+ (funcall notmuch-saved-search-sort-function saved-alist)))
(widget-insert "\nSaved searches: ")
(widget-create 'push-button
:notify (lambda (&rest ignore)
@@ -478,36 +522,36 @@ Complete list of currently available key bindings:
(widget-insert "\n\n")
(let ((start (point))
(nth 0))
- (mapc '(lambda (search)
- (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth))))
- (set widget-symbol
- (widget-create 'editable-field
- ;; Don't let the search boxes be
- ;; less than 8 characters wide.
- :size (max 8
- (- (window-width)
- ;; Leave some space
- ;; at the start and
- ;; end of the
- ;; boxes.
- (* 2 notmuch-hello-indent)
- ;; 1 for the space
- ;; before the
- ;; `[save]' button. 6
- ;; for the `[save]'
- ;; button.
- 1 6))
- :action (lambda (widget &rest ignore)
- (notmuch-hello-search (widget-value widget)))
- search))
- (widget-insert " ")
- (widget-create 'push-button
- :notify (lambda (widget &rest ignore)
- (notmuch-hello-add-saved-search widget))
- :notmuch-saved-search-widget widget-symbol
- "save"))
- (widget-insert "\n")
- (setq nth (1+ nth)))
+ (mapc (lambda (search)
+ (let ((widget-symbol (intern (format "notmuch-hello-search-%d" nth))))
+ (set widget-symbol
+ (widget-create 'editable-field
+ ;; Don't let the search boxes be
+ ;; less than 8 characters wide.
+ :size (max 8
+ (- (window-width)
+ ;; Leave some space
+ ;; at the start and
+ ;; end of the
+ ;; boxes.
+ (* 2 notmuch-hello-indent)
+ ;; 1 for the space
+ ;; before the
+ ;; `[save]' button. 6
+ ;; for the `[save]'
+ ;; button.
+ 1 6))
+ :action (lambda (widget &rest ignore)
+ (notmuch-hello-search (widget-value widget)))
+ search))
+ (widget-insert " ")
+ (widget-create 'push-button
+ :notify (lambda (widget &rest ignore)
+ (notmuch-hello-add-saved-search widget))
+ :notmuch-saved-search-widget widget-symbol
+ "save"))
+ (widget-insert "\n")
+ (setq nth (1+ nth)))
notmuch-hello-recent-searches)
(indent-rigidly start (point) notmuch-hello-indent)))
@@ -555,7 +599,9 @@ Complete list of currently available key bindings:
(widget-forward 1)))
(unless (widget-at)
- (notmuch-hello-goto-search)))))
+ (notmuch-hello-goto-search))))
+
+ (run-hooks 'notmuch-hello-refresh-hook))
(defun notmuch-folder ()
"Deprecated function for invoking notmuch---calling `notmuch' is preferred now."
diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el
index e6788685..6fbf82d2 100644
--- a/emacs/notmuch-maildir-fcc.el
+++ b/emacs/notmuch-maildir-fcc.el
@@ -65,8 +65,8 @@ yet when sending a mail."
;; Set up the message-fcc-handler to move mails to the maildir in Fcc
;; The parameter is set to mark messages as "seen"
(setq message-fcc-handler-function
- '(lambda (destdir)
- (notmuch-maildir-fcc-write-buffer-to-maildir destdir t)))
+ (lambda (destdir)
+ (notmuch-maildir-fcc-write-buffer-to-maildir destdir t)))
;; add a hook to actually insert the Fcc header when sending
(add-hook 'message-header-setup-hook 'notmuch-fcc-header-setup))
@@ -131,10 +131,10 @@ will NOT be removed or replaced."
(defun notmuch-maildir-fcc-host-fixer (hostname)
(replace-regexp-in-string "/\\|:"
- '(lambda (s)
- (cond ((string-equal s "/") "\\057")
- ((string-equal s ":") "\\072")
- (t s)))
+ (lambda (s)
+ (cond ((string-equal s "/") "\\057")
+ ((string-equal s ":") "\\072")
+ (t s)))
hostname
t
t))
diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el
index aefd3fbc..08e5b174 100644
--- a/emacs/notmuch-message.el
+++ b/emacs/notmuch-message.el
@@ -39,10 +39,10 @@ the \"inbox\" and \"todo\", you would set
(when (and notmuch-message-replied-tags rep)
;; add a "+" to any tag that is doesn't already begin with a "+"
;; or "-"
- (let ((tags (mapcar '(lambda (str)
- (if (not (string-match "^[+-]" str))
- (concat "+" str)
- str))
+ (let ((tags (mapcar (lambda (str)
+ (if (not (string-match "^[+-]" str))
+ (concat "+" str)
+ str))
notmuch-message-replied-tags)))
(apply 'notmuch-tag (concat "id:" (car (car rep))) tags)))))
diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
index 8824b080..7114e48a 100644
--- a/emacs/notmuch-mua.el
+++ b/emacs/notmuch-mua.el
@@ -35,10 +35,12 @@
"Function used to generate a `User-Agent:' string. If this is
`nil' then no `User-Agent:' will be generated."
:group 'notmuch
- :type 'function
- :options '(notmuch-mua-user-agent-full
- notmuch-mua-user-agent-notmuch
- notmuch-mua-user-agent-emacs))
+ :type '(choice (const :tag "No user agent string" nil)
+ (const :tag "Full" notmuch-mua-user-agent-full)
+ (const :tag "Notmuch" notmuch-mua-user-agent-notmuch)
+ (const :tag "Emacs" notmuch-mua-user-agent-emacs)
+ (function :tag "Custom user agent function"
+ :value notmuch-mua-user-agent-full)))
(defcustom notmuch-mua-hidden-headers '("^User-Agent:")
"Headers that are added to the `message-mode' hidden headers
@@ -124,9 +126,10 @@ list."
(message-goto-to))
-(defun notmuch-mua-mail (&optional to subject other-headers continue
- switch-function yank-action send-actions)
- "Invoke the notmuch mail composition window."
+(defun notmuch-mua-mail (&optional to subject other-headers &rest other-args)
+ "Invoke the notmuch mail composition window.
+
+OTHER-ARGS are passed through to `message-mail'."
(interactive)
(when notmuch-mua-user-agent-function
@@ -138,8 +141,7 @@ list."
(push (cons "From" (concat
(notmuch-user-name) " <" (notmuch-user-primary-email) ">")) other-headers))
- (message-mail to subject other-headers continue
- switch-function yank-action send-actions)
+ (apply #'message-mail to subject other-headers other-args)
(message-sort-headers)
(message-hide-headers)
(set-buffer-modified-p nil)
diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
index d5c95d80..82d11c92 100644
--- a/emacs/notmuch-show.el
+++ b/emacs/notmuch-show.el
@@ -91,6 +91,16 @@ any given message."
:group 'notmuch
:type 'boolean)
+(defcustom notmuch-show-indent-messages-width 1
+ "Width of message indentation in threads.
+
+Messages are shown indented according to their depth in a thread.
+This variable determines the width of this indentation measured
+in number of blanks. Defaults to `1', choose `0' to disable
+indentation."
+ :group 'notmuch
+ :type 'integer)
+
(defcustom notmuch-show-indent-multipart nil
"Should the sub-parts of a multipart/* part be indented?"
;; dme: Not sure which is a good default.
@@ -238,7 +248,7 @@ unchanged ADDRESS if parsing fails."
"Insert a notmuch style headerline based on HEADERS for a
message at DEPTH in the current thread."
(let ((start (point)))
- (insert (notmuch-show-spaces-n depth)
+ (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
(notmuch-show-clean-address (plist-get headers :From))
" ("
date
@@ -255,12 +265,12 @@ message at DEPTH in the current thread."
(defun notmuch-show-insert-headers (headers)
"Insert the headers of the current message."
(let ((start (point)))
- (mapc '(lambda (header)
- (let* ((header-symbol (intern (concat ":" header)))
- (header-value (plist-get headers header-symbol)))
- (if (and header-value
- (not (string-equal "" header-value)))
- (notmuch-show-insert-header header header-value))))
+ (mapc (lambda (header)
+ (let* ((header-symbol (intern (concat ":" header)))
+ (header-value (plist-get headers header-symbol)))
+ (if (and header-value
+ (not (string-equal "" header-value)))
+ (notmuch-show-insert-header header header-value))))
notmuch-message-headers)
(save-excursion
(save-restriction
@@ -310,17 +320,17 @@ message at DEPTH in the current thread."
;; ange-ftp, which is reasonable to use here.
(mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t)))))
-(defun notmuch-show-mm-display-part-inline (msg part content-type content)
+(defun notmuch-show-mm-display-part-inline (msg part nth content-type)
"Use the mm-decode/mm-view functions to display a part in the
current buffer, if possible."
(let ((display-buffer (current-buffer)))
(with-temp-buffer
- (insert content)
(let ((handle (mm-make-handle (current-buffer) (list content-type))))
- (set-buffer display-buffer)
(if (and (mm-inlinable-p handle)
(mm-inlined-p handle))
- (progn
+ (let ((content (notmuch-show-get-bodypart-content msg part nth)))
+ (insert content)
+ (set-buffer display-buffer)
(mm-display-part handle)
t)
nil)))))
@@ -334,7 +344,7 @@ current buffer, if possible."
))
(defun notmuch-show-multipart/*-to-list (part)
- (mapcar '(lambda (inner-part) (plist-get inner-part :content-type))
+ (mapcar (lambda (inner-part) (plist-get inner-part :content-type))
(plist-get part :content)))
(defun notmuch-show-multipart/alternative-choose (types)
@@ -447,11 +457,10 @@ current buffer, if possible."
(defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth declared-type)
(let ((button (notmuch-show-insert-part-header nth declared-type content-type nil)))
- (button-put button 'face '(:foreground "blue"))
+ (button-put button 'face 'notmuch-crypto-part-header)
;; add signature status button if sigstatus provided
(if (plist-member part :sigstatus)
- (let* ((headers (plist-get msg :headers))
- (from (plist-get headers :From))
+ (let* ((from (notmuch-show-get-header :From msg))
(sigstatus (car (plist-get part :sigstatus))))
(notmuch-crypto-insert-sigstatus-button sigstatus from))
;; if we're not adding sigstatus, tell the user how they can get it
@@ -470,15 +479,14 @@ current buffer, if possible."
(defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth declared-type)
(let ((button (notmuch-show-insert-part-header nth declared-type content-type nil)))
- (button-put button 'face '(:foreground "blue"))
+ (button-put button 'face 'notmuch-crypto-part-header)
;; add encryption status button if encstatus specified
(if (plist-member part :encstatus)
(let ((encstatus (car (plist-get part :encstatus))))
(notmuch-crypto-insert-encstatus-button encstatus)
;; add signature status button if sigstatus specified
(if (plist-member part :sigstatus)
- (let* ((headers (plist-get msg :headers))
- (from (plist-get headers :From))
+ (let* ((from (notmuch-show-get-header :From msg))
(sigstatus (car (plist-get part :sigstatus))))
(notmuch-crypto-insert-sigstatus-button sigstatus from))))
;; if we're not adding encstatus, tell the user how they can get it
@@ -511,7 +519,6 @@ current buffer, if possible."
(defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth declared-type)
(notmuch-show-insert-part-header nth declared-type content-type nil)
(let* ((message (car (plist-get part :content)))
- (headers (plist-get message :headers))
(body (car (plist-get message :body)))
(start (point)))
@@ -578,17 +585,10 @@ current buffer, if possible."
nil))
nil))))
-(defun notmuch-show-insert-part-application/* (msg part content-type nth depth declared-type
-)
- ;; do not render random "application" parts
- (notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename)))
-
(defun notmuch-show-insert-part-*/* (msg part content-type nth depth declared-type)
;; This handler _must_ succeed - it is the handler of last resort.
(notmuch-show-insert-part-header nth content-type declared-type (plist-get part :filename))
- (let ((content (notmuch-show-get-bodypart-content msg part nth)))
- (if content
- (notmuch-show-mm-display-part-inline msg part content-type content)))
+ (notmuch-show-mm-display-part-inline msg part nth content-type)
t)
;; Functions for determining how to handle MIME parts.
@@ -657,7 +657,7 @@ current buffer, if possible."
(defun notmuch-show-insert-body (msg body depth)
"Insert the body BODY at depth DEPTH in the current thread."
- (mapc '(lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
+ (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
(defun notmuch-show-make-symbol (type)
(make-symbol (concat "notmuch-show-" type)))
@@ -739,7 +739,7 @@ current buffer, if possible."
(setq content-end (point-marker))
;; Indent according to the depth in the thread.
- (indent-rigidly content-start content-end depth)
+ (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth))
(setq message-end (point-max-marker))
@@ -775,11 +775,11 @@ current buffer, if possible."
(defun notmuch-show-insert-thread (thread depth)
"Insert the thread THREAD at depth DEPTH in the current forest."
- (mapc '(lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
+ (mapc (lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
(defun notmuch-show-insert-forest (forest)
"Insert the forest of threads FOREST."
- (mapc '(lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
+ (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
(defvar notmuch-show-thread-id nil)
(make-variable-buffer-local 'notmuch-show-thread-id)
@@ -803,7 +803,7 @@ a corresponding notmuch search."
(remove-overlays (match-beginning 0) (match-end 0) 'goto-address t)
(make-text-button (match-beginning 0) (match-end 0)
'action `(lambda (arg)
- (notmuch-search ,(match-string-no-properties 0)))
+ (notmuch-show ,(match-string-no-properties 0)))
'follow-link t
'help-echo "Mouse-1, RET: search for this message"
'face goto-address-mail-face))))
@@ -843,6 +843,8 @@ buffer."
(inhibit-read-only t))
(switch-to-buffer buffer)
(notmuch-show-mode)
+ ;; Don't track undo information for this buffer
+ (set 'buffer-undo-list t)
(setq notmuch-show-thread-id thread-id)
(setq notmuch-show-parent-buffer parent-buffer)
@@ -1051,6 +1053,12 @@ All currently available key bindings:
(put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
(defun notmuch-show-get-message-properties ()
+ "Return the properties of the current message as a plist.
+
+Some useful entries are:
+:headers - Property list containing the headers :Date, :Subject, :From, etc.
+:body - Body of the message
+:tags - Tags for this message"
(save-excursion
(notmuch-show-move-to-message-top)
(get-text-property (point) :notmuch-message-properties)))
@@ -1077,9 +1085,9 @@ All currently available key bindings:
"Return the filename of the current message."
(notmuch-show-get-prop :filename))
-(defun notmuch-show-get-header (header)
+(defun notmuch-show-get-header (header &optional props)
"Return the named header of the current message, if any."
- (plist-get (notmuch-show-get-prop :headers) header))
+ (plist-get (notmuch-show-get-prop :headers props) header))
(defun notmuch-show-get-cc ()
(notmuch-show-get-header :Cc))
@@ -1135,30 +1143,23 @@ All currently available key bindings:
;; Commands typically bound to keys.
-(defun notmuch-show-advance-and-archive ()
- "Advance through thread and archive.
-
-This command is intended to be one of the simplest ways to
-process a thread of email. It does the following:
+(defun notmuch-show-advance ()
+ "Advance through thread.
If the current message in the thread is not yet fully visible,
scroll by a near screenful to read more of the message.
Otherwise, (the end of the current message is already within the
-current window), advance to the next open message.
-
-Finally, if there is no further message to advance to, and this
-last message is already read, then archive the entire current
-thread, (remove the \"inbox\" tag from each message). Also kill
-this buffer, and display the next thread from the search from
-which this thread was originally shown."
+current window), advance to the next open message."
(interactive)
(let* ((end-of-this-message (notmuch-show-message-bottom))
- (visible-end-of-this-message (1- end-of-this-message)))
+ (visible-end-of-this-message (1- end-of-this-message))
+ (ret nil))
(while (invisible-p visible-end-of-this-message)
(setq visible-end-of-this-message
- (previous-single-char-property-change visible-end-of-this-message
- 'invisible)))
+ (max (point-min)
+ (1- (previous-single-char-property-change
+ visible-end-of-this-message 'invisible)))))
(cond
;; Ideally we would test `end-of-this-message' against the result
;; of `window-end', but that doesn't account for the fact that
@@ -1173,8 +1174,24 @@ which this thread was originally shown."
(notmuch-show-next-open-message))
(t
- ;; This is the last message - archive the thread.
- (notmuch-show-archive-thread)))))
+ ;; This is the last message - change the return value
+ (setq ret t)))
+ ret))
+
+(defun notmuch-show-advance-and-archive ()
+ "Advance through thread and archive.
+
+This command is intended to be one of the simplest ways to
+process a thread of email. It works exactly like
+notmuch-show-advance, in that it scrolls through messages in a
+show buffer, except that when it gets to the end of the buffer it
+archives the entire current thread, (remove the \"inbox\" tag
+from each message), kills the buffer, and displays the next
+thread from the search from which this thread was originally
+shown."
+ (interactive)
+ (if (notmuch-show-advance)
+ (notmuch-show-archive-thread)))
(defun notmuch-show-rewind ()
"Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
diff --git a/emacs/notmuch.el b/emacs/notmuch.el
index c1827cc2..fde23779 100644
--- a/emacs/notmuch.el
+++ b/emacs/notmuch.el
@@ -646,9 +646,9 @@ This function advances the next thread when finished."
Here is an example of how to color search results based on tags.
(the following text would be placed in your ~/.emacs file):
- (setq notmuch-search-line-faces '((\"delete\" . '(:foreground \"red\"
- :background \"blue\"))
- (\"unread\" . '(:foreground \"green\"))))
+ (setq notmuch-search-line-faces '((\"delete\" . (:foreground \"red\"
+ :background \"blue\"))
+ (\"unread\" . (:foreground \"green\"))))
The attributes defined for matching tags are merged, with later
attributes overriding earlier. A message having both \"delete\"
@@ -662,16 +662,16 @@ foreground and blue background."
;; Create the overlay only if the message has tags which match one
;; of those specified in `notmuch-search-line-faces'.
(let (overlay)
- (mapc '(lambda (elem)
- (let ((tag (car elem))
- (attributes (cdr elem)))
- (when (member tag line-tag-list)
- (when (not overlay)
- (setq overlay (make-overlay start end)))
- ;; Merge the specified properties with any already
- ;; applied from an earlier match.
- (overlay-put overlay 'face
- (append (overlay-get overlay 'face) attributes)))))
+ (mapc (lambda (elem)
+ (let ((tag (car elem))
+ (attributes (cdr elem)))
+ (when (member tag line-tag-list)
+ (when (not overlay)
+ (setq overlay (make-overlay start end)))
+ ;; Merge the specified properties with any already
+ ;; applied from an earlier match.
+ (overlay-put overlay 'face
+ (append (overlay-get overlay 'face) attributes)))))
notmuch-search-line-faces)))
(defun notmuch-search-author-propertize (authors)
@@ -805,12 +805,12 @@ non-authors is found, assume that all of the authors match."
(goto-char (point-max))
(if (/= (match-beginning 1) line)
(insert (concat "Error: Unexpected output from notmuch search:\n" (substring string line (match-beginning 1)) "\n")))
- (let ((beg (point-marker)))
+ (let ((beg (point)))
(notmuch-search-show-result date count authors subject tags)
- (notmuch-search-color-line beg (point-marker) tag-list)
- (put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id)
- (put-text-property beg (point-marker) 'notmuch-search-authors authors)
- (put-text-property beg (point-marker) 'notmuch-search-subject subject)
+ (notmuch-search-color-line beg (point) tag-list)
+ (put-text-property beg (point) 'notmuch-search-thread-id thread-id)
+ (put-text-property beg (point) 'notmuch-search-authors authors)
+ (put-text-property beg (point) 'notmuch-search-subject subject)
(if (string= thread-id notmuch-search-target-thread)
(progn
(set 'found-target beg)
@@ -885,7 +885,7 @@ PROMPT is the string to prompt with."
"subject:" "attachment:")
(mapcar (lambda (tag)
(concat "tag:" tag))
- (process-lines "notmuch" "search" "--output=tags" "*")))))
+ (process-lines notmuch-command "search" "--output=tags" "*")))))
(let ((keymap (copy-keymap minibuffer-local-map))
(minibuffer-completion-table
(completion-table-dynamic
@@ -920,6 +920,8 @@ The optional parameters are used as follows:
(let ((buffer (get-buffer-create (notmuch-search-buffer-title query))))
(switch-to-buffer buffer)
(notmuch-search-mode)
+ ;; Don't track undo information for this buffer
+ (set 'buffer-undo-list t)
(set 'notmuch-search-query-string query)
(set 'notmuch-search-oldest-first oldest-first)
(set 'notmuch-search-target-thread target-thread)
@@ -963,28 +965,43 @@ same relative position within the new buffer."
(notmuch-search query oldest-first target-thread target-line continuation)
(goto-char (point-min))))
-(defcustom notmuch-poll-script ""
+(defcustom notmuch-poll-script nil
"An external script to incorporate new mail into the notmuch database.
-If this variable is non empty, then it should name a script to be
-invoked by `notmuch-search-poll-and-refresh-view' and
+This variable controls the action invoked by
+`notmuch-search-poll-and-refresh-view' and
`notmuch-hello-poll-and-update' (each have a default keybinding
-of 'G'). The script could do any of the following depending on
+of 'G') to incorporate new mail into the notmuch database.
+
+If set to nil (the default), new mail is processed by invoking
+\"notmuch new\". Otherwise, this should be set to a string that
+gives the name of an external script that processes new mail. If
+set to the empty string, no command will be run.
+
+The external script could do any of the following depending on
the user's needs:
1. Invoke a program to transfer mail to the local mail store
2. Invoke \"notmuch new\" to incorporate the new mail
-3. Invoke one or more \"notmuch tag\" commands to classify the mail"
- :type 'string
+3. Invoke one or more \"notmuch tag\" commands to classify the mail
+
+Note that the recommended way of achieving the same is using
+\"notmuch new\" hooks."
+ :type '(choice (const :tag "notmuch new" nil)
+ (const :tag "Disabled" "")
+ (string :tag "Custom script"))
:group 'notmuch)
(defun notmuch-poll ()
- "Run external script to import mail.
+ "Run \"notmuch new\" or an external script to import mail.
-Invokes `notmuch-poll-script' if it is not set to an empty string."
+Invokes `notmuch-poll-script', \"notmuch new\", or does nothing
+depending on the value of `notmuch-poll-script'."
(interactive)
- (if (not (string= notmuch-poll-script ""))
- (call-process notmuch-poll-script nil nil)))
+ (if (stringp notmuch-poll-script)
+ (if (not (string= notmuch-poll-script ""))
+ (call-process notmuch-poll-script nil nil))
+ (call-process notmuch-command nil nil nil "new")))
(defun notmuch-search-poll-and-refresh-view ()
"Invoke `notmuch-poll' to import mail, then refresh the current view."
@@ -1038,6 +1055,23 @@ current search results AND that are tagged with the given tag."
(interactive)
(notmuch-hello))
+;;;###autoload
+(defun notmuch-jump-to-recent-buffer ()
+ "Jump to the most recent notmuch buffer (search, show or hello).
+
+If no recent buffer is found, run `notmuch'."
+ (interactive)
+ (let ((last
+ (loop for buffer in (buffer-list)
+ if (with-current-buffer buffer
+ (memq major-mode '(notmuch-show-mode
+ notmuch-search-mode
+ notmuch-hello-mode)))
+ return buffer)))
+ (if last
+ (switch-to-buffer last)
+ (notmuch))))
+
(setq mail-user-agent 'notmuch-user-agent)
(provide 'notmuch)
diff --git a/hooks.c b/hooks.c
new file mode 100644
index 00000000..44ee4198
--- /dev/null
+++ b/hooks.c
@@ -0,0 +1,93 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2011 Jani Nikula
+ *
+ * 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 http://www.gnu.org/licenses/ .
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#include "notmuch-client.h"
+#include <sys/wait.h>
+
+int
+notmuch_run_hook (const char *db_path, const char *hook)
+{
+ char *hook_path;
+ int status = 0;
+ pid_t pid;
+
+ hook_path = talloc_asprintf (NULL, "%s/%s/%s/%s", db_path, ".notmuch",
+ "hooks", hook);
+ if (hook_path == NULL) {
+ fprintf (stderr, "Out of memory\n");
+ return 1;
+ }
+
+ /* Check access before fork() for speed and simplicity of error handling. */
+ if (access (hook_path, X_OK) == -1) {
+ /* Ignore ENOENT. It's okay not to have a hook, hook dir, or even
+ * notmuch dir. Dangling symbolic links also result in ENOENT, but
+ * we'll ignore that too for simplicity. */
+ if (errno != ENOENT) {
+ fprintf (stderr, "Error: %s hook access failed: %s\n", hook,
+ strerror (errno));
+ status = 1;
+ }
+ goto DONE;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ fprintf (stderr, "Error: %s hook fork failed: %s\n", hook,
+ strerror (errno));
+ status = 1;
+ goto DONE;
+ } else if (pid == 0) {
+ execl (hook_path, hook_path, NULL);
+ /* Same as above for ENOENT, but unlikely now. Indicate all other errors
+ * to parent through non-zero exit status. */
+ if (errno != ENOENT) {
+ fprintf (stderr, "Error: %s hook execution failed: %s\n", hook,
+ strerror (errno));
+ status = 1;
+ }
+ exit (status);
+ }
+
+ if (waitpid (pid, &status, 0) == -1) {
+ fprintf (stderr, "Error: %s hook wait failed: %s\n", hook,
+ strerror (errno));
+ status = 1;
+ goto DONE;
+ }
+
+ if (!WIFEXITED (status) || WEXITSTATUS (status)) {
+ if (WIFEXITED (status)) {
+ fprintf (stderr, "Error: %s hook failed with status %d\n",
+ hook, WEXITSTATUS (status));
+ } else if (WIFSIGNALED (status)) {
+ fprintf (stderr, "Error: %s hook terminated with signal %d\n",
+ hook, WTERMSIG (status));
+ }
+ status = 1;
+ }
+
+ DONE:
+ talloc_free (hook_path);
+
+ return status;
+}
diff --git a/lib/Makefile.local b/lib/Makefile.local
index 57dca702..54c4dea4 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -30,7 +30,7 @@ LIBRARY_SUFFIX = so
LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX)
SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR)
LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE)
-LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME)
+LIBRARY_LINK_FLAG = -shared -Wl,--version-script=notmuch.sym,-soname=$(SONAME) -Wl,--no-undefined
ifeq ($(LIBDIR_IN_LDCONFIG),1)
ifeq ($(DESTDIR),)
LIBRARY_INSTALL_POST_COMMAND=ldconfig
diff --git a/lib/database.cc b/lib/database.cc
index 98f101e6..8103bd96 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -28,6 +28,8 @@
#include <glib.h> /* g_free, GPtrArray, GHashTable */
#include <glib-object.h> /* g_type_init */
+#include <gmime/gmime.h> /* g_mime_init */
+
using namespace std;
#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
@@ -81,13 +83,17 @@ typedef struct {
* STRING is the name of a file within that
* directory for this mail message.
*
- * A mail document also has two values:
+ * A mail document also has four values:
*
* TIMESTAMP: The time_t value corresponding to the message's
* Date header.
*
* MESSAGE_ID: The unique ID of the mail mess (see "id" above)
*
+ * FROM: The value of the "From" header
+ *
+ * SUBJECT: The value of the "Subject" header
+ *
* In addition, terms from the content of the message are added with
* "from", "to", "attachment", and "subject" prefixes for use by the
* user in searching. Similarly, terms from the path of the mail
@@ -581,6 +587,7 @@ notmuch_database_open (const char *path,
struct stat st;
int err;
unsigned int i, version;
+ static int initialized = 0;
if (asprintf (&notmuch_path, "%s/%s", path, ".notmuch") == -1) {
notmuch_path = NULL;
@@ -604,6 +611,12 @@ notmuch_database_open (const char *path,
/* Initialize the GLib type system and threads */
g_type_init ();
+ /* Initialize gmime */
+ if (! initialized) {
+ g_mime_init (0);
+ initialized = 1;
+ }
+
notmuch = talloc (NULL, notmuch_database_t);
notmuch->exception_reported = FALSE;
notmuch->path = talloc_strdup (notmuch, path);
@@ -1447,7 +1460,7 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
keys = g_hash_table_get_keys (parents);
for (l = keys; l; l = l->next) {
char *parent_message_id;
- const char *parent_thread_id;
+ const char *parent_thread_id = NULL;
parent_message_id = (char *) l->data;
diff --git a/lib/message.cc b/lib/message.cc
index ca7fbf21..00754254 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -49,16 +49,16 @@ struct visible _notmuch_message {
struct maildir_flag_tag {
char flag;
const char *tag;
- bool inverse;
+ notmuch_bool_t inverse;
};
/* ASCII ordered table of Maildir flags and associated tags */
static struct maildir_flag_tag flag2tag[] = {
- { 'D', "draft", false},
- { 'F', "flagged", false},
- { 'P', "passed", false},
- { 'R', "replied", false},
- { 'S', "unread", true }
+ { 'D', "draft", FALSE},
+ { 'F', "flagged", FALSE},
+ { 'P', "passed", FALSE},
+ { 'R', "replied", FALSE},
+ { 'S', "unread", TRUE }
};
/* We end up having to call the destructor explicitly because we had
@@ -1217,8 +1217,6 @@ _new_maildir_filename (void *ctx,
if (info == NULL) {
info = filename + strlen(filename);
} else {
- flags = info + 3;
-
/* Loop through existing flags in filename. */
for (flags = info + 3, last_flag = 0;
*flags;
diff --git a/notmuch-client.h b/notmuch-client.h
index b50cb38b..c602e2e0 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -235,7 +235,11 @@ void
notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
notmuch_bool_t synchronize_flags);
+int
+notmuch_run_hook (const char *db_path, const char *hook);
+
notmuch_bool_t
debugger_is_active (void);
+#include "command-line-arguments.h"
#endif
diff --git a/notmuch-config.c b/notmuch-config.c
index 1a7ed580..d697138a 100644
--- a/notmuch-config.c
+++ b/notmuch-config.c
@@ -22,6 +22,7 @@
#include <pwd.h>
#include <netdb.h>
+#include <assert.h>
static const char toplevel_config_comment[] =
" .notmuch-config - Configuration file for the notmuch mail system\n"
@@ -520,92 +521,80 @@ notmuch_config_set_user_primary_email (notmuch_config_t *config,
config->user_primary_email = NULL;
}
-const char **
-notmuch_config_get_user_other_email (notmuch_config_t *config,
- size_t *length)
+static const char **
+_config_get_list (notmuch_config_t *config,
+ const char *section, const char *key,
+ const char ***outlist, size_t *list_length, size_t *ret_length)
{
- char **emails;
- size_t emails_length;
- unsigned int i;
+ assert(outlist);
+
+ if (*outlist == NULL) {
+
+ char **inlist = g_key_file_get_string_list (config->key_file,
+ section, key, list_length, NULL);
+ if (inlist) {
+ unsigned int i;
+
+ *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
- if (config->user_other_email == NULL) {
- emails = g_key_file_get_string_list (config->key_file,
- "user", "other_email",
- &emails_length, NULL);
- if (emails) {
- config->user_other_email = talloc_size (config,
- sizeof (char *) *
- (emails_length + 1));
- for (i = 0; i < emails_length; i++)
- config->user_other_email[i] = talloc_strdup (config->user_other_email,
- emails[i]);
- config->user_other_email[i] = NULL;
+ for (i = 0; i < *list_length; i++)
+ (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
- g_strfreev (emails);
+ (*outlist)[i] = NULL;
- config->user_other_email_length = emails_length;
+ g_strfreev (inlist);
}
}
- *length = config->user_other_email_length;
- return config->user_other_email;
+ if (ret_length)
+ *ret_length = *list_length;
+
+ return *outlist;
}
-void
-notmuch_config_set_user_other_email (notmuch_config_t *config,
- const char *other_email[],
- size_t length)
+const char **
+notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
{
- g_key_file_set_string_list (config->key_file,
- "user", "other_email",
- other_email, length);
-
- talloc_free (config->user_other_email);
- config->user_other_email = NULL;
+ return _config_get_list (config, "user", "other_email",
+ &(config->user_other_email),
+ &(config->user_other_email_length), length);
}
const char **
-notmuch_config_get_new_tags (notmuch_config_t *config,
- size_t *length)
+notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
{
- char **tags;
- size_t tags_length;
- unsigned int i;
-
- if (config->new_tags == NULL) {
- tags = g_key_file_get_string_list (config->key_file,
- "new", "tags",
- &tags_length, NULL);
- if (tags) {
- config->new_tags = talloc_size (config,
- sizeof (char *) *
- (tags_length + 1));
- for (i = 0; i < tags_length; i++)
- config->new_tags[i] = talloc_strdup (config->new_tags,
- tags[i]);
- config->new_tags[i] = NULL;
-
- g_strfreev (tags);
+ return _config_get_list (config, "new", "tags",
+ &(config->new_tags),
+ &(config->new_tags_length), length);
+}
- config->new_tags_length = tags_length;
- }
- }
+static void
+_config_set_list (notmuch_config_t *config,
+ const char *group, const char *name,
+ const char *list[],
+ size_t length, const char ***config_var )
+{
+ g_key_file_set_string_list (config->key_file, group, name, list, length);
+ talloc_free (*config_var);
+ *config_var = NULL;
+}
- *length = config->new_tags_length;
- return config->new_tags;
+void
+notmuch_config_set_user_other_email (notmuch_config_t *config,
+ const char *list[],
+ size_t length)
+{
+ _config_set_list (config, "user", "other_email", list, length,
+ &(config->user_other_email));
}
void
notmuch_config_set_new_tags (notmuch_config_t *config,
- const char *new_tags[],
- size_t length)
+ const char *list[],
+ size_t length)
{
- g_key_file_set_string_list (config->key_file,
- "new", "tags",
- new_tags, length);
-
- talloc_free (config->new_tags);
- config->new_tags = NULL;
+ _config_set_list (config, "new", "tags", list, length,
+ &(config->new_tags));
}
/* Given a configuration item of the form <group>.<key> return the
diff --git a/notmuch-dump.c b/notmuch-dump.c
index 126593d1..a7358756 100644
--- a/notmuch-dump.c
+++ b/notmuch-dump.c
@@ -41,39 +41,49 @@ notmuch_dump_command (unused (void *ctx), int argc, char *argv[])
if (notmuch == NULL)
return 1;
- argc--; argv++; /* skip subcommand argument */
+ char *output_file_name = NULL;
+ int opt_index;
- if (argc && strcmp (argv[0], "--") != 0) {
+ notmuch_opt_desc_t options[] = {
+ { NOTMUCH_OPT_POSITION, &output_file_name, 0, 0, 0 },
+ { 0, 0, 0, 0, 0 }
+ };
+
+ opt_index = parse_arguments (argc, argv, options, 1);
+
+ if (opt_index < 0) {
+ /* diagnostics already printed */
+ return 1;
+ }
+
+ if (output_file_name) {
fprintf (stderr, "Warning: the output file argument of dump is deprecated.\n");
- output = fopen (argv[0], "w");
+ output = fopen (output_file_name, "w");
if (output == NULL) {
fprintf (stderr, "Error opening %s for writing: %s\n",
- argv[0], strerror (errno));
+ output_file_name, strerror (errno));
return 1;
}
- argc--;
- argv++;
}
- if (argc && strcmp (argv[0], "--") == 0){
- argc--;
- argv++;
- }
- if (argc) {
- query_str = query_string_from_args (notmuch, argc, argv);
+ if (opt_index < argc) {
+ query_str = query_string_from_args (notmuch, argc-opt_index, argv+opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
return 1;
}
}
-
+
query = notmuch_query_create (notmuch, query_str);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
return 1;
}
- notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
+ /* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the
+ * first results quickly at the expense of total time.
+ */
+ notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
for (messages = notmuch_query_search_messages (query);
notmuch_messages_valid (messages);
diff --git a/notmuch-new.c b/notmuch-new.c
index 81a93500..3512de72 100644
--- a/notmuch-new.c
+++ b/notmuch-new.c
@@ -67,7 +67,7 @@ handle_sigint (unused (int sig))
{
static char msg[] = "Stopping... \n";
- write(2, msg, sizeof(msg)-1);
+ (void) write(2, msg, sizeof(msg)-1);
interrupted = 1;
}
@@ -811,6 +811,7 @@ notmuch_new_command (void *ctx, int argc, char *argv[])
_filename_node_t *f;
int i;
notmuch_bool_t timer_is_active = FALSE;
+ notmuch_bool_t run_hooks = TRUE;
add_files_state.verbose = 0;
add_files_state.output_is_a_tty = isatty (fileno (stdout));
@@ -820,6 +821,8 @@ notmuch_new_command (void *ctx, int argc, char *argv[])
for (i = 0; i < argc && argv[i][0] == '-'; i++) {
if (STRNCMP_LITERAL (argv[i], "--verbose") == 0) {
add_files_state.verbose = 1;
+ } else if (strcmp (argv[i], "--no-hooks") == 0) {
+ run_hooks = FALSE;
} else {
fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
return 1;
@@ -833,6 +836,12 @@ notmuch_new_command (void *ctx, int argc, char *argv[])
add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
db_path = notmuch_config_get_database_path (config);
+ if (run_hooks) {
+ ret = notmuch_run_hook (db_path, "pre-new");
+ if (ret)
+ return ret;
+ }
+
dot_notmuch_path = talloc_asprintf (ctx, "%s/%s", db_path, ".notmuch");
if (stat (dot_notmuch_path, &st)) {
@@ -981,5 +990,8 @@ notmuch_new_command (void *ctx, int argc, char *argv[])
notmuch_database_close (notmuch);
+ if (run_hooks && !ret && !interrupted)
+ ret = notmuch_run_hook (db_path, "post-new");
+
return ret || interrupted;
}
diff --git a/notmuch-reply.c b/notmuch-reply.c
index 7ac879f9..7242310a 100644
--- a/notmuch-reply.c
+++ b/notmuch-reply.c
@@ -622,11 +622,9 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
char *opt, *query_string;
int i, ret = 0;
int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params);
- notmuch_show_params_t params;
+ notmuch_show_params_t params = { .part = -1 };
reply_format_func = notmuch_reply_format_default;
- params.part = -1;
- params.cryptoctx = NULL;
argc--; argv++; /* skip subcommand argument */
@@ -648,10 +646,12 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])
} else if ((STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) {
if (params.cryptoctx == NULL) {
GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL);
- if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg")))
+ if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg"))) {
fprintf (stderr, "Failed to construct gpg context.\n");
- else
+ } else {
+ params.decrypt = TRUE;
g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE);
+ }
g_object_unref (session);
session = NULL;
}
diff --git a/notmuch-restore.c b/notmuch-restore.c
index 13b4325a..87d9772b 100644
--- a/notmuch-restore.c
+++ b/notmuch-restore.c
@@ -18,8 +18,6 @@
* Author: Carl Worth <cworth@cworth.org>
*/
-#include <getopt.h>
-
#include "notmuch-client.h"
int
@@ -29,12 +27,14 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])
notmuch_database_t *notmuch;
notmuch_bool_t synchronize_flags;
notmuch_bool_t accumulate = FALSE;
+ char *input_file_name = NULL;
FILE *input = stdin;
char *line = NULL;
size_t line_size;
ssize_t line_len;
regex_t regex;
int rerr;
+ int opt_index;
config = notmuch_config_open (ctx, NULL, NULL);
if (config == NULL)
@@ -47,37 +47,30 @@ notmuch_restore_command (unused (void *ctx), int argc, char *argv[])
synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
- struct option options[] = {
- { "accumulate", no_argument, 0, 'a' },
- { 0, 0, 0, 0}
+ notmuch_opt_desc_t options[] = {
+ { NOTMUCH_OPT_POSITION, &input_file_name, 0, 0, 0 },
+ { NOTMUCH_OPT_BOOLEAN, &accumulate, "accumulate", 'a', 0 },
+ { 0, 0, 0, 0, 0 }
};
- int opt;
- do {
- opt = getopt_long (argc, argv, "", options, NULL);
-
- switch (opt) {
- case 'a':
- accumulate = 1;
- break;
- case '?':
- return 1;
- break;
- }
+ opt_index = parse_arguments (argc, argv, options, 1);
- } while (opt != -1);
+ if (opt_index < 0) {
+ /* diagnostics already printed */
+ return 1;
+ }
- if (optind < argc) {
- input = fopen (argv[optind], "r");
+ if (input_file_name) {
+ input = fopen (input_file_name, "r");
if (input == NULL) {
fprintf (stderr, "Error opening %s for reading: %s\n",
- argv[optind], strerror (errno));
+ input_file_name, strerror (errno));
return 1;
}
optind++;
}
- if (optind < argc) {
+ if (opt_index < argc) {
fprintf (stderr,
"Cannot read dump from more than one file: %s\n",
argv[optind]);
diff --git a/notmuch-search.c b/notmuch-search.c
index 36686d19..4baab561 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -176,12 +176,14 @@ format_thread_json (const void *ctx,
printf ("\"thread\": %s,\n"
"\"timestamp\": %ld,\n"
+ "\"date_relative\": \"%s\",\n"
"\"matched\": %d,\n"
"\"total\": %d,\n"
"\"authors\": %s,\n"
"\"subject\": %s,\n",
json_quote_str (ctx_quote, thread_id),
date,
+ notmuch_time_relative_date (ctx, date),
matched,
total,
json_quote_str (ctx_quote, authors),
@@ -415,81 +417,51 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
notmuch_database_t *notmuch;
notmuch_query_t *query;
char *query_str;
- char *opt;
notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
const search_format_t *format = &format_text;
- int i, ret;
+ int opt_index, ret;
output_t output = OUTPUT_SUMMARY;
int offset = 0;
int limit = -1; /* unlimited */
- argc--; argv++; /* skip subcommand argument */
+ enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT }
+ format_sel = NOTMUCH_FORMAT_TEXT;
- for (i = 0; i < argc && argv[i][0] == '-'; i++) {
- if (strcmp (argv[i], "--") == 0) {
- i++;
- break;
- }
- if (STRNCMP_LITERAL (argv[i], "--sort=") == 0) {
- opt = argv[i] + sizeof ("--sort=") - 1;
- if (strcmp (opt, "oldest-first") == 0) {
- sort = NOTMUCH_SORT_OLDEST_FIRST;
- } else if (strcmp (opt, "newest-first") == 0) {
- sort = NOTMUCH_SORT_NEWEST_FIRST;
- } else {
- fprintf (stderr, "Invalid value for --sort: %s\n", opt);
- return 1;
- }
- } else if (STRNCMP_LITERAL (argv[i], "--offset=") == 0) {
- char *p;
- opt = argv[i] + sizeof ("--offset=") - 1;
- offset = strtol (opt, &p, 10);
- if (*opt == '\0' || p == opt || *p != '\0') {
- fprintf (stderr, "Invalid value for --offset: %s\n", opt);
- return 1;
- }
- } else if (STRNCMP_LITERAL (argv[i], "--limit=") == 0) {
- char *p;
- opt = argv[i] + sizeof ("--limit=") - 1;
- limit = strtoul (opt, &p, 10);
- if (*opt == '\0' || p == opt || *p != '\0') {
- fprintf (stderr, "Invalid value for --limit: %s\n", opt);
- return 1;
- }
- } else if (STRNCMP_LITERAL (argv[i], "--format=") == 0) {
- opt = argv[i] + sizeof ("--format=") - 1;
- if (strcmp (opt, "text") == 0) {
- format = &format_text;
- } else if (strcmp (opt, "json") == 0) {
- format = &format_json;
- } else {
- fprintf (stderr, "Invalid value for --format: %s\n", opt);
- return 1;
- }
- } else if (STRNCMP_LITERAL (argv[i], "--output=") == 0) {
- opt = argv[i] + sizeof ("--output=") - 1;
- if (strcmp (opt, "summary") == 0) {
- output = OUTPUT_SUMMARY;
- } else if (strcmp (opt, "threads") == 0) {
- output = OUTPUT_THREADS;
- } else if (strcmp (opt, "messages") == 0) {
- output = OUTPUT_MESSAGES;
- } else if (strcmp (opt, "files") == 0) {
- output = OUTPUT_FILES;
- } else if (strcmp (opt, "tags") == 0) {
- output = OUTPUT_TAGS;
- } else {
- fprintf (stderr, "Invalid value for --output: %s\n", opt);
- return 1;
- }
- } else {
- fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
- return 1;
- }
+ notmuch_opt_desc_t options[] = {
+ { NOTMUCH_OPT_KEYWORD, &sort, "sort", 's',
+ (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
+ { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
+ { 0, 0 } } },
+ { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
+ (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+ { "text", NOTMUCH_FORMAT_TEXT },
+ { 0, 0 } } },
+ { NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
+ (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
+ { "threads", OUTPUT_THREADS },
+ { "messages", OUTPUT_MESSAGES },
+ { "files", OUTPUT_FILES },
+ { "tags", OUTPUT_TAGS },
+ { 0, 0 } } },
+ { NOTMUCH_OPT_INT, &offset, "offset", 'O', 0 },
+ { NOTMUCH_OPT_INT, &limit, "limit", 'L', 0 },
+ { 0, 0, 0, 0, 0 }
+ };
+
+ opt_index = parse_arguments (argc, argv, options, 1);
+
+ if (opt_index < 0) {
+ return 1;
}
- argc -= i;
- argv += i;
+ switch (format_sel) {
+ case NOTMUCH_FORMAT_TEXT:
+ format = &format_text;
+ break;
+ case NOTMUCH_FORMAT_JSON:
+ format = &format_json;
+ break;
+ }
config = notmuch_config_open (ctx, NULL, NULL);
if (config == NULL)
@@ -500,7 +472,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
if (notmuch == NULL)
return 1;
- query_str = query_string_from_args (notmuch, argc, argv);
+ query_str = query_string_from_args (notmuch, argc-opt_index, argv+opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
return 1;
diff --git a/notmuch-show.c b/notmuch-show.c
index 603992a6..19fb49f2 100644
--- a/notmuch-show.c
+++ b/notmuch-show.c
@@ -255,7 +255,9 @@ _extract_email_address (const void *ctx, const char *from)
email = talloc_strdup (ctx, email);
DONE:
- /* XXX: How to free addresses here? */
+ if (addresses)
+ g_object_unref (addresses);
+
return email;
}
@@ -469,9 +471,12 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out)
/* This result can be NULL for things like "unknown-8bit".
* Don't set a NULL filter as that makes GMime print
* annoying assertion-failure messages on stderr. */
- if (charset_filter)
+ if (charset_filter) {
g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
charset_filter);
+ g_object_unref (charset_filter);
+ }
+
}
wrapper = g_mime_part_get_content_object (GMIME_PART (part));
@@ -861,7 +866,7 @@ do_show_single (void *ctx,
while (!feof (file)) {
size = fread (buf, 1, sizeof (buf), file);
- fwrite (buf, size, 1, stdout);
+ (void) fwrite (buf, size, 1, stdout);
}
fclose (file);
diff --git a/notmuch-tag.c b/notmuch-tag.c
index dded39ea..292c5da3 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -26,10 +26,85 @@ static void
handle_sigint (unused (int sig))
{
static char msg[] = "Stopping... \n";
- write(2, msg, sizeof(msg)-1);
+ (void) write(2, msg, sizeof(msg)-1);
interrupted = 1;
}
+static char *
+_escape_tag (char *buf, const char *tag)
+{
+ const char *in = tag;
+ char *out = buf;
+ /* Boolean terms surrounded by double quotes can contain any
+ * character. Double quotes are quoted by doubling them. */
+ *out++ = '"';
+ while (*in) {
+ if (*in == '"')
+ *out++ = '"';
+ *out++ = *in++;
+ }
+ *out++ = '"';
+ *out = 0;
+ return buf;
+}
+
+static char *
+_optimize_tag_query (void *ctx, const char *orig_query_string, char *argv[],
+ int *add_tags, int add_tags_count,
+ int *remove_tags, int remove_tags_count)
+{
+ /* This is subtler than it looks. Xapian ignores the '-' operator
+ * at the beginning both queries and parenthesized groups and,
+ * furthermore, the presence of a '-' operator at the beginning of
+ * a group can inhibit parsing of the previous operator. Hence,
+ * the user-provided query MUST appear first, but it is safe to
+ * parenthesize and the exclusion part of the query must not use
+ * the '-' operator (though the NOT operator is fine). */
+
+ char *escaped, *query_string;
+ const char *join = "";
+ int i;
+ unsigned int max_tag_len = 0;
+
+ /* Allocate a buffer for escaping tags. This is large enough to
+ * hold a fully escaped tag with every character doubled plus
+ * enclosing quotes and a NUL. */
+ for (i = 0; i < add_tags_count; i++)
+ if (strlen (argv[add_tags[i]] + 1) > max_tag_len)
+ max_tag_len = strlen (argv[add_tags[i]] + 1);
+ for (i = 0; i < remove_tags_count; i++)
+ if (strlen (argv[remove_tags[i]] + 1) > max_tag_len)
+ max_tag_len = strlen (argv[remove_tags[i]] + 1);
+ escaped = talloc_array(ctx, char, max_tag_len * 2 + 3);
+ if (!escaped)
+ return NULL;
+
+ /* Build the new query string */
+ if (strcmp (orig_query_string, "*") == 0)
+ query_string = talloc_strdup (ctx, "(");
+ else
+ query_string = talloc_asprintf (ctx, "( %s ) and (", orig_query_string);
+
+ for (i = 0; i < add_tags_count && query_string; i++) {
+ query_string = talloc_asprintf_append_buffer (
+ query_string, "%snot tag:%s", join,
+ _escape_tag (escaped, argv[add_tags[i]] + 1));
+ join = " or ";
+ }
+ for (i = 0; i < remove_tags_count && query_string; i++) {
+ query_string = talloc_asprintf_append_buffer (
+ query_string, "%stag:%s", join,
+ _escape_tag (escaped, argv[remove_tags[i]] + 1));
+ join = " or ";
+ }
+
+ if (query_string)
+ query_string = talloc_strdup_append_buffer (query_string, ")");
+
+ talloc_free (escaped);
+ return query_string;
+}
+
int
notmuch_tag_command (void *ctx, unused (int argc), unused (char *argv[]))
{
@@ -93,6 +168,16 @@ notmuch_tag_command (void *ctx, unused (int argc), unused (char *argv[]))
return 1;
}
+ /* Optimize the query so it excludes messages that already have
+ * the specified set of tags. */
+ query_string = _optimize_tag_query (ctx, query_string, argv,
+ add_tags, add_tags_count,
+ remove_tags, remove_tags_count);
+ if (query_string == NULL) {
+ fprintf (stderr, "Out of memory.\n");
+ return 1;
+ }
+
config = notmuch_config_open (ctx, NULL, NULL);
if (config == NULL)
return 1;
diff --git a/notmuch.1 b/notmuch.1
index 147319e5..a5828bc5 100644
--- a/notmuch.1
+++ b/notmuch.1
@@ -16,7 +16,7 @@
.\" along with this program. If not, see http://www.gnu.org/licenses/ .
.\"
.\" Author: Carl Worth <cworth@cworth.org>
-.TH NOTMUCH 1 2011-12-04 "Notmuch 0.10.2"
+.TH NOTMUCH 1 2012-01-13 "Notmuch 0.11"
.SH NAME
notmuch \- thread-based email index, search, and tagging
.SH SYNOPSIS
@@ -85,7 +85,7 @@ The
command is used to incorporate new mail into the notmuch database.
.RS 4
.TP 4
-.B new
+.BR new " [options...]"
Find and import any new messages to the database.
@@ -118,6 +118,22 @@ if
has previously been completed, but
.B "notmuch new"
has not previously been run.
+
+The
+.B new
+command supports hooks. See the
+.B "HOOKS"
+section below for more details on hooks.
+
+Supported options for
+.B new
+include
+.RS 4
+.TP 4
+.BR \-\-no\-hooks
+
+Prevents hooks from being run.
+.RE
.RE
Several of the notmuch commands accept search terms with a common
@@ -705,6 +721,38 @@ specify a date range to return messages from 2009\-10\-01 until the
current time:
$(date +%s \-d 2009\-10\-01)..$(date +%s)
+.SH HOOKS
+Hooks are scripts (or arbitrary executables or symlinks to such) that notmuch
+invokes before and after certain actions. These scripts reside in
+the .notmuch/hooks directory within the database directory and must have
+executable permissions.
+
+The currently available hooks are described below.
+.RS 4
+.TP 4
+.B pre\-new
+This hook is invoked by the
+.B new
+command before scanning or importing new messages into the database. If this
+hook exits with a non-zero status, notmuch will abort further processing of the
+.B new
+command.
+
+Typically this hook is used for fetching or delivering new mail to be imported
+into the database.
+.RE
+.RS 4
+.TP 4
+.B post\-new
+This hook is invoked by the
+.B new
+command after new messages have been imported into the database and initial tags
+have been applied. The hook will not be run if there have been any errors during
+the scan or import.
+
+Typically this hook is used to perform additional query\-based tagging on the
+imported messages.
+.RE
.SH ENVIRONMENT
The following environment variables can be used to control the
behavior of notmuch.
diff --git a/notmuch.c b/notmuch.c
index d44ce9a0..c0ce026a 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -127,6 +127,32 @@ static const char search_terms_help[] =
"\n"
"\t\t$(date +%%s -d 2009-10-01)..$(date +%%s)\n\n";
+static const char hooks_help[] =
+ "\tHooks are scripts (or arbitrary executables or symlinks to such) that\n"
+ "\tnotmuch invokes before and after certain actions. These scripts reside\n"
+ "\tin the .notmuch/hooks directory within the database directory and must\n"
+ "\thave executable permissions.\n"
+ "\n"
+ "\tThe currently available hooks are described below.\n"
+ "\n"
+ "\tpre-new\n"
+ "\t\tThis hook is invoked by the new command before scanning or\n"
+ "\t\timporting new messages into the database. If this hook exits\n"
+ "\t\twith a non-zero status, notmuch will abort further processing\n"
+ "\t\tof the new command.\n"
+ "\n"
+ "\t\tTypically this hook is used for fetching or delivering new\n"
+ "\t\tmail to be imported into the database.\n"
+ "\n"
+ "\tpost-new\n"
+ "\t\tThis hook is invoked by the new command after new messages\n"
+ "\t\thave been imported into the database and initial tags have\n"
+ "\t\tbeen applied. The hook will not be run if there have been any\n"
+ "\t\terrors during the scan or import.\n"
+ "\n"
+ "\t\tTypically this hook is used to perform additional query-based\n"
+ "\t\ttagging on the imported messages.\n\n";
+
static command_t commands[] = {
{ "setup", notmuch_setup_command,
NULL,
@@ -144,7 +170,7 @@ static command_t commands[] = {
"\tInvoking notmuch with no command argument will run setup if\n"
"\tthe setup command has not previously been completed." },
{ "new", notmuch_new_command,
- "[--verbose]",
+ "[options...]",
"Find and import new messages to the notmuch database.",
"\tScans all sub-directories of the mail directory, performing\n"
"\tfull-text indexing on new messages that are found. Each new\n"
@@ -159,8 +185,15 @@ static command_t commands[] = {
"\tis delivered and you wish to incorporate it into the database.\n"
"\tThese subsequent runs will be much quicker than the initial run.\n"
"\n"
+ "\tThe new command supports hooks. See \"notmuch help hooks\" for\n"
+ "\tmore details on hooks.\n"
+ "\n"
"\tSupported options for new include:\n"
"\n"
+ "\t--no-hooks\n"
+ "\n"
+ "\t\tPrevent hooks from being run.\n"
+ "\n"
"\t--verbose\n"
"\n"
"\t\tVerbose operation. Shows paths of message files as\n"
@@ -529,6 +562,10 @@ notmuch_help_command (unused (void *ctx), int argc, char *argv[])
printf ("\n");
printf (search_terms_help);
return 0;
+ } else if (strcmp (argv[0], "hooks") == 0) {
+ printf ("Help for <%s>\n\n", argv[0]);
+ printf (hooks_help);
+ return 0;
}
fprintf (stderr,
diff --git a/test/.gitignore b/test/.gitignore
index 9e97052d..e63c689d 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -1,4 +1,6 @@
test-results
corpus.mail
smtp-dummy
+symbol-test
+arg-test
tmp.*
diff --git a/test/Makefile.local b/test/Makefile.local
index 8eb04330..fa2df734 100644
--- a/test/Makefile.local
+++ b/test/Makefile.local
@@ -2,19 +2,32 @@
dir := test
+extra_cflags += -I.
+
smtp_dummy_srcs = \
$(notmuch_compat_srcs) \
$(dir)/smtp-dummy.c
smtp_dummy_modules = $(smtp_dummy_srcs:.c=.o)
+$(dir)/arg-test: $(dir)/arg-test.o command-line-arguments.o util/libutil.a
+ $(call quiet,CC) -I. $^ -o $@
+
$(dir)/smtp-dummy: $(smtp_dummy_modules)
$(call quiet,CC) $^ -o $@
+$(dir)/symbol-test: $(dir)/symbol-test.o
+ $(call quiet,CXX) $^ -o $@ -Llib -lnotmuch -lxapian
+
.PHONY: test check
-test: all $(dir)/smtp-dummy
+
+test-binaries: $(dir)/arg-test $(dir)/smtp-dummy $(dir)/symbol-test
+
+test: all test-binaries
@${dir}/notmuch-test $(OPTIONS)
check: test
-CLEAN := $(CLEAN) $(dir)/smtp-dummy
+CLEAN := $(CLEAN) $(dir)/smtp-dummy $(dir)/smtp-dummy.o \
+ $(dir)/symbol-test $(dir)/symbol-test.o \
+ $(dir)/arg-test $(dir)/arg-test.o
diff --git a/test/README b/test/README
index 2481f16d..7b2e96d4 100644
--- a/test/README
+++ b/test/README
@@ -13,7 +13,8 @@ notmuch-test script). Either command will run all available tests.
Alternately, you can run a specific subset of tests by simply invoking
one of the executable scripts in this directory, (such as ./search,
-./reply, etc.)
+./reply, etc). Note that you will probably want "make test-binaries"
+before running individual tests.
The following command-line options are available when running tests:
@@ -187,8 +188,8 @@ library for your script to use.
is to summarize successes and failures in the test script and
exit with an appropriate error code.
-There are also a number of mail-specific functions which are useful in
-writing tests:
+There are also a number of notmuch-specific auxiliary functions and
+variables which are useful in writing tests:
generate_message
@@ -212,3 +213,15 @@ writing tests:
will initialize the mail database to a known state of 50 sample
messages, (culled from the early history of the notmuch mailing
list).
+
+ notmuch_counter_reset
+ $notmuch_counter_command
+ notmuch_counter_value
+
+ These allow to count how many times notmuch binary is called.
+ notmuch_counter_reset() function generates a script that counts
+ how many times it is called and resets the counter to zero. The
+ function sets $notmuch_counter_command variable to the path to the
+ generated script that should be called instead of notmuch to do
+ the counting. The notmuch_counter_value() function prints the
+ current counter value.
diff --git a/test/arg-test.c b/test/arg-test.c
new file mode 100644
index 00000000..adc56e30
--- /dev/null
+++ b/test/arg-test.c
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include "command-line-arguments.h"
+
+
+int main(int argc, char **argv){
+
+ int opt_index=1;
+
+ int kw_val=0;
+ int int_val=0;
+ char *pos_arg1=NULL;
+ char *pos_arg2=NULL;
+ char *string_val=NULL;
+
+ notmuch_opt_desc_t options[] = {
+ { NOTMUCH_OPT_KEYWORD, &kw_val, "keyword", 'k',
+ (notmuch_keyword_t []){ { "one", 1 },
+ { "two", 2 },
+ { 0, 0 } } },
+ { NOTMUCH_OPT_INT, &int_val, "int", 'i', 0},
+ { NOTMUCH_OPT_STRING, &string_val, "string", 's', 0},
+ { NOTMUCH_OPT_POSITION, &pos_arg1, 0,0, 0},
+ { NOTMUCH_OPT_POSITION, &pos_arg2, 0,0, 0},
+ { 0, 0, 0, 0, 0 } };
+
+ opt_index = parse_arguments(argc, argv, options, 1);
+
+ if (opt_index < 0)
+ return 1;
+
+ if (kw_val)
+ printf("keyword %d\n", kw_val);
+
+ if (int_val)
+ printf("int %d\n", int_val);
+
+ if (string_val)
+ printf("string %s\n", string_val);
+
+ if (pos_arg1)
+ printf("positional arg 1 %s\n", pos_arg1);
+
+ if (pos_arg2)
+ printf("positional arg 2 %s\n", pos_arg1);
+
+
+ for ( ; opt_index < argc ; opt_index ++) {
+ printf("non parsed arg %d = %s\n", opt_index, argv[opt_index]);
+ }
+
+ return 0;
+}
diff --git a/test/argument-parsing b/test/argument-parsing
new file mode 100755
index 00000000..672de0b3
--- /dev/null
+++ b/test/argument-parsing
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+test_description="argument parsing"
+. ./test-lib.sh
+
+test_begin_subtest "sanity check"
+$TEST_DIRECTORY/arg-test pos1 --keyword=one --string=foo pos2 --int=7 > OUTPUT
+cat <<EOF > EXPECTED
+keyword 1
+int 7
+string foo
+positional arg 1 pos1
+positional arg 2 pos1
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_done
diff --git a/test/atomicity b/test/atomicity
index ad7d4a3c..6df0a00e 100755
--- a/test/atomicity
+++ b/test/atomicity
@@ -7,8 +7,7 @@ test_description='atomicity'
# final database contents should be the same regardless of when (or
# if) it is killed and restarted.
-if which gdb 1>/dev/null 2>&1; then
- test_set_prereq GDB
+if test_require_external_prereq gdb; then
# Create a maildir structure to also stress flag synchronization
mkdir $MAIL_DIR/cur
@@ -91,14 +90,11 @@ if which gdb 1>/dev/null 2>&1; then
i=$(expr $end - 1)
fi
done
-else
- say_color info "%-6s" "WARNING"
- echo " Missing test prerequisite GDB"
-fi
+fi
test_begin_subtest '"notmuch new" is idempotent under arbitrary aborts'
-test_expect_equal_file GDB searchall expectall
+test_expect_equal_file searchall expectall
-test_expect_success GDB "detected $outcount>10 abort points" "test $outcount -gt 10"
+test_expect_success "detected $outcount>10 abort points" "test $outcount -gt 10"
test_done
diff --git a/test/basic b/test/basic
index 38db2baf..d6aed24c 100755
--- a/test/basic
+++ b/test/basic
@@ -51,17 +51,11 @@ test_expect_code 2 'failure to clean up causes the test to fail' '
# Ensure that all tests are being run
test_begin_subtest 'Ensure that all available tests will be run by notmuch-test'
-eval $(sed -n -e '/^TESTS="$/,/^"$/p' notmuch-test $TEST_DIRECTORY/notmuch-test)
+eval $(sed -n -e '/^TESTS="$/,/^"$/p' $TEST_DIRECTORY/notmuch-test)
tests_in_suite=$(for i in $TESTS; do echo $i; done | sort)
-available=$(ls -1 $TEST_DIRECTORY/ | \
- sed -r -e "/^(aggregate-results.sh|Makefile|Makefile.local|notmuch-test)/d" \
- -e "/^(README|test-lib.sh|test-lib.el|test-results|tmp.*|valgrind|corpus*)/d" \
- -e "/^(emacs.expected-output|smtp-dummy|smtp-dummy.c|test-verbose|symbol-test.cc)/d" \
- -e "/^(test.expected-output|.*~)/d" \
- -e "/^(gnupg-secret-key.asc)/d" \
- -e "/^(gnupg-secret-key.NOTE)/d" \
- -e "/^(atomicity.gdb)/d" \
- | sort)
+available=$(find "$TEST_DIRECTORY" -maxdepth 1 -type f -executable -printf '%f\n' | \
+ sed -r -e "/^(aggregate-results.sh|notmuch-test|smtp-dummy|test-verbose|symbol-test|arg-test)$/d" | \
+ sort)
test_expect_equal "$tests_in_suite" "$available"
EXPECTED=$TEST_DIRECTORY/test.expected-output
diff --git a/test/emacs b/test/emacs
index 75a0a744..f36718e7 100755
--- a/test/emacs
+++ b/test/emacs
@@ -50,6 +50,27 @@ test_emacs "(notmuch-show \"$maildir_storage_thread\")
(test-output)"
test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage
+test_begin_subtest "Basic notmuch-show view in emacs default indentation"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(let ((notmuch-show-indent-messages-width 1))
+ (notmuch-show \"$maildir_storage_thread\")
+ (test-output))"
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage
+
+test_begin_subtest "Basic notmuch-show view in emacs without indentation"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(let ((notmuch-show-indent-messages-width 0))
+ (notmuch-show \"$maildir_storage_thread\")
+ (test-output))"
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage-without-indentation
+
+test_begin_subtest "Basic notmuch-show view in emacs with fourfold indentation"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(let ((notmuch-show-indent-messages-width 4))
+ (notmuch-show \"$maildir_storage_thread\")
+ (test-output))"
+test_expect_equal_file OUTPUT $EXPECTED/notmuch-show-thread-maildir-storage-with-fourfold-indentation
+
test_begin_subtest "notmuch-show for message with invalid From"
add_message "[subject]=\"message-with-invalid-from\"" \
"[from]=\"\\\"Invalid \\\" From\\\" <test_suite@notmuchmail.org>\""
@@ -369,8 +390,18 @@ test_emacs '(notmuch-show "id:\"bought\"")
(rotate-yank-pointer 1))
(reverse-region (point-min) (point-max))
(test-output)'
-sed -i -e 's/^.*tmp.emacs\/mail.*$/FILENAME/' OUTPUT
-test_expect_equal_file OUTPUT $EXPECTED/emacs-stashing
+cat <<EOF >EXPECTED
+Sat, 01 Jan 2000 12:00:00 -0000
+Some One <someone@somewhere.org>
+Some One Else <notsomeone@somewhere.org>
+Notmuch <notmuch@notmuchmail.org>
+Stash my stashables
+id:"bought"
+bought
+inbox,stashtest
+${gen_msg_filename}
+EOF
+test_expect_equal_file OUTPUT EXPECTED
test_begin_subtest "Stashing in notmuch-search"
test_emacs '(notmuch-search "id:\"bought\"")
@@ -381,33 +412,7 @@ test_emacs '(notmuch-search "id:\"bought\"")
(yank)
(test-output)'
sed -i -e 's/^thread:.*$/thread:XXX/' OUTPUT
-test_expect_equal $(cat OUTPUT) "thread:XXX"
-
-test_begin_subtest 'Hiding message following HTML part'
-test_subtest_known_broken
-id='html-message@notmuchmail.org'
-emacs_deliver_message \
- 'HTML message' \
- '' \
- "(message-goto-eoh)
- (insert \"Message-ID: <$id>\n\")
- (message-goto-body)
- (mml-insert-part \"text/html\")
- (insert \"<body>This is a test HTML message</body>\")"
-emacs_deliver_message \
- 'Reply to HTML message' \
- 'This is a reply to the test HTML message' \
- "(message-goto-eoh)
- (insert \"In-Reply-To: <$id>\n\")"
-test_emacs "(notmuch-show \"id:$id\") \
- (notmuch-show-next-message) \
- (command-execute (key-binding (kbd \"RET\"))) \
- (test-visible-output)"
-test_emacs "(notmuch-show \"id:$id\") \
- (notmuch-show-next-message) \
- (notmuch-show-toggle-message) \
- (test-visible-output \"EXPECTED\")"
-test_expect_equal_file OUTPUT EXPECTED
+test_expect_equal "$(cat OUTPUT)" "thread:XXX"
test_begin_subtest 'notmuch-show-advance-and-archive with invisible signature'
message1='id:20091118010116.GC25380@dottiness.seas.harvard.edu'
@@ -441,4 +446,72 @@ test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.
(test-visible-output)'
test_expect_equal_file OUTPUT EXPECTED
+test_begin_subtest "Do not call notmuch for non-inlinable application/mpeg parts"
+id='message-with-application/mpeg-attachment@notmuchmail.org'
+emacs_deliver_message \
+ 'Message with application/mpeg attachment' \
+ '' \
+ "(message-goto-eoh)
+ (insert \"Message-ID: <$id>\n\")
+ (message-goto-body)
+ (mml-insert-part \"application/mpeg\")
+ (insert \"a fake mp3 file\")"
+notmuch_counter_reset
+test_emacs "(let ((notmuch-command \"$notmuch_counter_command\"))
+ (notmuch-show \"id:$id\"))"
+test_expect_equal $(notmuch_counter_value) 1
+
+test_begin_subtest "Do not call notmuch for non-inlinable audio/mpeg parts"
+id='message-with-audio/mpeg-attachment@notmuchmail.org'
+emacs_deliver_message \
+ 'Message with audio/mpeg attachment' \
+ '' \
+ "(message-goto-eoh)
+ (insert \"Message-ID: <$id>\n\")
+ (message-goto-body)
+ (mml-insert-part \"audio/mpeg\")
+ (insert \"a fake mp3 file\")"
+notmuch_counter_reset
+test_emacs "(let ((notmuch-command \"$notmuch_counter_command\"))
+ (notmuch-show \"id:$id\"))"
+test_expect_equal $(notmuch_counter_value) 1
+
+test_begin_subtest "notmuch-hello-mode hook is called"
+counter=$(test_emacs \
+ '(let ((notmuch-hello-mode-hook-counter 0))
+ (kill-buffer "*notmuch-hello*")
+ (notmuch-hello)
+ notmuch-hello-mode-hook-counter)'
+)
+test_expect_equal "$counter" 1
+
+test_begin_subtest "notmuch-hello-mode hook is not called on updates"
+counter=$(test_emacs \
+ '(let ((notmuch-hello-mode-hook-counter 0))
+ (kill-buffer "*notmuch-hello*")
+ (notmuch-hello)
+ (notmuch-hello-update)
+ notmuch-hello-mode-hook-counter)'
+)
+test_expect_equal "$counter" 1
+
+test_begin_subtest "notmuch-hello-refresh hook is called"
+counter=$(test_emacs \
+ '(let ((notmuch-hello-refresh-hook-counter 0))
+ (kill-buffer "*notmuch-hello*")
+ (notmuch-hello)
+ notmuch-hello-refresh-hook-counter)'
+)
+test_expect_equal "$counter" 1
+
+test_begin_subtest "notmuch-hello-refresh hook is called on updates"
+counter=$(test_emacs \
+ '(let ((notmuch-hello-refresh-hook-counter 0))
+ (kill-buffer "*notmuch-hello*")
+ (notmuch-hello)
+ (notmuch-hello-update)
+ notmuch-hello-refresh-hook-counter)'
+)
+test_expect_equal "$counter" 2
+
test_done
diff --git a/test/emacs.expected-output/emacs-stashing b/test/emacs.expected-output/emacs-stashing
deleted file mode 100644
index 49235947..00000000
--- a/test/emacs.expected-output/emacs-stashing
+++ /dev/null
@@ -1,9 +0,0 @@
-Sat, 01 Jan 2000 12:00:00 -0000
-Some One <someone@somewhere.org>
-Some One Else <notsomeone@somewhere.org>
-Notmuch <notmuch@notmuchmail.org>
-Stash my stashables
-id:"bought"
-bought
-inbox,stashtest
-FILENAME
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
new file mode 100644
index 00000000..41e2aaa3
--- /dev/null
+++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
@@ -0,0 +1,215 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch. I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+ notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+ Error opening
+ /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 5-line signature. Click/Enter to show. ]
+--
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+ Subject: Re: [notmuch] Working with Maildir storage?
+ To: notmuch@notmuchmail.org
+ Date: Wed, 18 Nov 2009 01:02:38 +0600
+
+ [ multipart/mixed ]
+ [ multipart/signed ]
+ [ text/plain ]
+
+ Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did gyre and gimble:
+
+ LK> Resulted in 4604 lines of errors along the lines of:
+
+ LK> Error opening
+ LK> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ LK> Too many open files
+
+ See the patch just posted here.
+
+ [ 2-line signature. Click/Enter to show. ]
+ --
+ http://fossarchy.blogspot.com/
+ [ application/pgp-signature ]
+ [ text/plain ]
+ [ 4-line signature. Click/Enter to show. ]
+ _______________________________________________
+ notmuch mailing list
+ notmuch@notmuchmail.org
+ http://notmuchmail.org/mailman/listinfo/notmuch
+ Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+ Subject: Re: [notmuch] Working with Maildir storage?
+ To: Mikhail Gusarov <dottedmag@dottedmag.net>
+ Cc: notmuch@notmuchmail.org
+ Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+ [ multipart/mixed ]
+ [ multipart/signed ]
+ [ text/plain ]
+ > See the patch just posted here.
+
+ Is the list archived anywhere? The obvious archives
+ (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+ think I subscribed too late to get the patch (I only just saw the
+ discussion about it).
+
+ It doesn't look like the patch is in git yet.
+
+ -- Lars
+
+ [ 5-line signature. Click/Enter to show. ]
+ --
+ Lars Kellogg-Stedman <lars@seas.harvard.edu>
+ Senior Technologist, Computing and Information Technology
+ Harvard University School of Engineering and Applied Sciences
+
+ [ application/pgp-signature ]
+ [ text/plain ]
+ [ 4-line signature. Click/Enter to show. ]
+ _______________________________________________
+ notmuch mailing list
+ notmuch@notmuchmail.org
+ http://notmuchmail.org/mailman/listinfo/notmuch
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+ Subject: [notmuch] Working with Maildir storage?
+ To: notmuch@notmuchmail.org
+ Date: Wed, 18 Nov 2009 02:50:48 +0600
+
+
+ Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did gyre and gimble:
+
+ LK> Is the list archived anywhere? The obvious archives
+ LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+ LK> think I subscribed too late to get the patch (I only just saw the
+ LK> discussion about it).
+
+ LK> It doesn't look like the patch is in git yet.
+
+ Just has been pushed
+
+ [ 10-line signature. Click/Enter to show. ]
+ --
+ http://fossarchy.blogspot.com/
+ -------------- next part --------------
+ A non-text attachment was scrubbed...
+ Name: not available
+ Type: application/pgp-signature
+ Size: 834 bytes
+ Desc: not available
+ URL: <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp>
+
+ Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+ Subject: [notmuch] Working with Maildir storage?
+ To: notmuch@notmuchmail.org
+ Date: Tue, 17 Nov 2009 13:24:13 -0800
+
+ On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote:
+ > > See the patch just posted here.
+
+ I've also pushed a slightly more complicated (and complete) fix to my
+ private notmuch repository
+
+ git://keithp.com/git/notmuch
+
+ > Is the list archived anywhere?
+
+ Oops. Looks like Carl's mail server is broken. He's traveling to
+ Barcelona today and so it won't get fixed for a while.
+
+ Thanks to everyone for trying out notmuch!
+
+ -keith
+
+ Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+ Subject: Re: [notmuch] Working with Maildir storage?
+ To: Keith Packard <keithp@keithp.com>
+ Cc: notmuch@notmuchmail.org
+ Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+ [ multipart/mixed ]
+ [ multipart/signed ]
+ [ text/plain ]
+ > I've also pushed a slightly more complicated (and complete) fix to my
+ > private notmuch repository
+
+ The version of lib/messages.cc in your repo doesn't build because it's
+ missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+ [ 5-line signature. Click/Enter to show. ]
+ --
+ Lars Kellogg-Stedman <lars@seas.harvard.edu>
+ Senior Technologist, Computing and Information Technology
+ Harvard University School of Engineering and Applied Sciences
+
+ [ application/pgp-signature ]
+ [ text/plain ]
+ [ 4-line signature. Click/Enter to show. ]
+ _______________________________________________
+ notmuch mailing list
+ notmuch@notmuchmail.org
+ http://notmuchmail.org/mailman/listinfo/notmuch
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+ Subject: [notmuch] Working with Maildir storage?
+ To: notmuch@notmuchmail.org
+ Date: Wed, 18 Nov 2009 02:08:10 -0800
+
+ On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote:
+ > I saw the LWN article and decided to take a look at notmuch. I'm
+ > currently using mutt and mairix to index and read a collection of
+ > Maildir mail folders (around 40,000 messages total).
+
+ Welcome, Lars!
+
+ I hadn't even seen that Keith's blog post had been picked up by lwn.net.
+ That's very interesting. So, thanks for coming and trying out notmuch.
+
+ > Error opening
+ > /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ > Too many open files
+
+ Sadly, the lwn article coincided with me having just introduced this
+ bug, and then getting on a Trans-Atlantic flight. So I fixed the bug
+ fairly quickly, but there was quite a bit of latency before I could push
+ the fix out. It should be fixed now.
+
+ > I'm curious if this is expected behavior (i.e., notmuch does not work
+ > with Maildir) or if something else is going on.
+
+ Notmuch works just fine with maildir---it's one of the things that it
+ likes the best.
+
+ Happy hacking,
+
+ -Carl
+
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
new file mode 100644
index 00000000..fa2108ef
--- /dev/null
+++ b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
@@ -0,0 +1,215 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch. I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+ notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+ Error opening
+ /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 5-line signature. Click/Enter to show. ]
+--
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 01:02:38 +0600
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ text/plain ]
+
+Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did gyre and gimble:
+
+ LK> Resulted in 4604 lines of errors along the lines of:
+
+ LK> Error opening
+ LK> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ LK> Too many open files
+
+See the patch just posted here.
+
+[ 2-line signature. Click/Enter to show. ]
+--
+http://fossarchy.blogspot.com/
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Cc: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ text/plain ]
+> See the patch just posted here.
+
+Is the list archived anywhere? The obvious archives
+(http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+think I subscribed too late to get the patch (I only just saw the
+discussion about it).
+
+It doesn't look like the patch is in git yet.
+
+-- Lars
+
+[ 5-line signature. Click/Enter to show. ]
+--
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:50:48 +0600
+
+
+Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did gyre and gimble:
+
+ LK> Is the list archived anywhere? The obvious archives
+ LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+ LK> think I subscribed too late to get the patch (I only just saw the
+ LK> discussion about it).
+
+ LK> It doesn't look like the patch is in git yet.
+
+Just has been pushed
+
+[ 10-line signature. Click/Enter to show. ]
+--
+http://fossarchy.blogspot.com/
+-------------- next part --------------
+A non-text attachment was scrubbed...
+Name: not available
+Type: application/pgp-signature
+Size: 834 bytes
+Desc: not available
+URL: <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp>
+
+Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 13:24:13 -0800
+
+On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote:
+> > See the patch just posted here.
+
+I've also pushed a slightly more complicated (and complete) fix to my
+private notmuch repository
+
+git://keithp.com/git/notmuch
+
+> Is the list archived anywhere?
+
+Oops. Looks like Carl's mail server is broken. He's traveling to
+Barcelona today and so it won't get fixed for a while.
+
+Thanks to everyone for trying out notmuch!
+
+-keith
+
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: Keith Packard <keithp@keithp.com>
+Cc: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ text/plain ]
+> I've also pushed a slightly more complicated (and complete) fix to my
+> private notmuch repository
+
+The version of lib/messages.cc in your repo doesn't build because it's
+missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+[ 5-line signature. Click/Enter to show. ]
+--
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:08:10 -0800
+
+On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote:
+> I saw the LWN article and decided to take a look at notmuch. I'm
+> currently using mutt and mairix to index and read a collection of
+> Maildir mail folders (around 40,000 messages total).
+
+Welcome, Lars!
+
+I hadn't even seen that Keith's blog post had been picked up by lwn.net.
+That's very interesting. So, thanks for coming and trying out notmuch.
+
+> Error opening
+> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+> Too many open files
+
+Sadly, the lwn article coincided with me having just introduced this
+bug, and then getting on a Trans-Atlantic flight. So I fixed the bug
+fairly quickly, but there was quite a bit of latency before I could push
+the fix out. It should be fixed now.
+
+> I'm curious if this is expected behavior (i.e., notmuch does not work
+> with Maildir) or if something else is going on.
+
+Notmuch works just fine with maildir---it's one of the things that it
+likes the best.
+
+Happy hacking,
+
+-Carl
+
diff --git a/test/hooks b/test/hooks
new file mode 100755
index 00000000..77e8569b
--- /dev/null
+++ b/test/hooks
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+test_description='hooks'
+. ./test-lib.sh
+
+HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
+
+create_echo_hook () {
+ local TOKEN="${RANDOM}"
+ mkdir -p ${HOOK_DIR}
+ cat <<EOF >"${HOOK_DIR}/${1}"
+#!/bin/sh
+echo "${TOKEN}" > ${3}
+EOF
+ chmod +x "${HOOK_DIR}/${1}"
+ echo "${TOKEN}" > ${2}
+}
+
+create_failing_hook () {
+ mkdir -p ${HOOK_DIR}
+ cat <<EOF >"${HOOK_DIR}/${1}"
+#!/bin/sh
+exit 13
+EOF
+ chmod +x "${HOOK_DIR}/${1}"
+}
+
+rm_hooks () {
+ rm -rf ${HOOK_DIR}
+}
+
+# add a message to generate mail dir and database
+add_message
+
+test_begin_subtest "pre-new is run"
+rm_hooks
+generate_message
+create_echo_hook "pre-new" expected output
+notmuch new > /dev/null
+test_expect_equal_file expected output
+
+test_begin_subtest "post-new is run"
+rm_hooks
+generate_message
+create_echo_hook "post-new" expected output
+notmuch new > /dev/null
+test_expect_equal_file expected output
+
+test_begin_subtest "pre-new is run before post-new"
+rm_hooks
+generate_message
+create_echo_hook "pre-new" pre-new.expected pre-new.output
+create_echo_hook "post-new" post-new.expected post-new.output
+notmuch new > /dev/null
+test_expect_equal_file post-new.expected post-new.output
+
+test_begin_subtest "pre-new non-zero exit status (hook status)"
+rm_hooks
+generate_message
+create_failing_hook "pre-new"
+output=`notmuch new 2>&1`
+test_expect_equal "$output" "Error: pre-new hook failed with status 13"
+
+# depends on the previous subtest leaving broken hook behind
+test_expect_code 1 "pre-new non-zero exit status (notmuch status)" "notmuch new"
+
+# depends on the previous subtests leaving 1 new message behind
+test_begin_subtest "pre-new non-zero exit status aborts new"
+rm_hooks
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "post-new non-zero exit status (hook status)"
+rm_hooks
+generate_message
+create_failing_hook "post-new"
+NOTMUCH_NEW 2>output.stderr >output
+cat output.stderr >> output
+echo "Added 1 new message to the database." > expected
+echo "Error: post-new hook failed with status 13" >> expected
+test_expect_equal_file expected output
+
+# depends on the previous subtest leaving broken hook behind
+test_expect_code 1 "post-new non-zero exit status (notmuch status)" "notmuch new"
+
+# test_begin_subtest "hook without executable permissions"
+rm_hooks
+mkdir -p ${HOOK_DIR}
+cat <<EOF >"${HOOK_DIR}/pre-new"
+#!/bin/sh
+echo foo
+EOF
+output=`notmuch new 2>&1`
+test_expect_code 1 "hook without executable permissions" "notmuch new"
+
+# test_begin_subtest "hook execution failure"
+rm_hooks
+mkdir -p ${HOOK_DIR}
+cat <<EOF >"${HOOK_DIR}/pre-new"
+no hashbang, execl fails
+EOF
+chmod +x "${HOOK_DIR}/pre-new"
+test_expect_code 1 "hook execution failure" "notmuch new"
+
+test_done
diff --git a/test/json b/test/json
index 592b0687..7df43803 100755
--- a/test/json
+++ b/test/json
@@ -12,6 +12,7 @@ add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00
output=$(notmuch search --format=json "json-search-message" | notmuch_search_sanitize)
test_expect_equal "$output" "[{\"thread\": \"XXX\",
\"timestamp\": 946728000,
+\"date_relative\": \"2000-01-01\",
\"matched\": 1,
\"total\": 1,
\"authors\": \"Notmuch Test Suite\",
@@ -41,6 +42,7 @@ add_message "[subject]=\"json-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Ja
output=$(notmuch search --format=json "jsön-search-méssage" | notmuch_search_sanitize)
test_expect_equal "$output" "[{\"thread\": \"XXX\",
\"timestamp\": 946728000,
+\"date_relative\": \"2000-01-01\",
\"matched\": 1,
\"total\": 1,
\"authors\": \"Notmuch Test Suite\",
diff --git a/test/notmuch-test b/test/notmuch-test
index 113ea7cf..ded79e8f 100755
--- a/test/notmuch-test
+++ b/test/notmuch-test
@@ -48,6 +48,8 @@ TESTS="
search-folder-coherence
atomicity
python
+ hooks
+ argument-parsing
"
TESTS=${NOTMUCH_TESTS:=$TESTS}
@@ -62,10 +64,13 @@ else
TEST_TIMEOUT_CMD=""
fi
+trap 'e=$?; kill $!; exit $e' HUP INT TERM
# Run the tests
for test in $TESTS; do
- $TEST_TIMEOUT_CMD ./$test "$@"
+ $TEST_TIMEOUT_CMD ./$test "$@" &
+ wait $!
done
+trap - HUP INT TERM
# Report results
./aggregate-results.sh test-results/*
diff --git a/test/python b/test/python
index f737749f..c3aa7266 100755
--- a/test/python
+++ b/test/python
@@ -5,9 +5,7 @@ test_description="python bindings"
add_email_corpus
test_begin_subtest "compare thread ids"
-LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib \
-PYTHONPATH=$TEST_DIRECTORY/../bindings/python \
-python <<EOF | sort > OUTPUT
+test_python <<EOF
import notmuch
db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
q_new = notmuch.Query(db, 'tag:inbox')
@@ -15,5 +13,5 @@ for t in q_new.search_threads():
print t.get_thread_id()
EOF
notmuch search --output=threads tag:inbox | sed s/^thread:// | sort > EXPECTED
-test_expect_equal_file OUTPUT EXPECTED
+test_expect_equal_file <(sort OUTPUT) EXPECTED
test_done
diff --git a/test/raw b/test/raw
index b7e265a8..99d3a3bf 100755
--- a/test/raw
+++ b/test/raw
@@ -1,4 +1,4 @@
-#!/usr//bin/env bash
+#!/usr/bin/env bash
test_description='notmuch show --format=raw'
. ./test-lib.sh
diff --git a/test/symbol-hiding b/test/symbol-hiding
index d0b31aec..68f0d1b1 100755
--- a/test/symbol-hiding
+++ b/test/symbol-hiding
@@ -12,15 +12,22 @@ test_description='exception symbol hiding'
. ./test-lib.sh
run_test(){
- result=$(LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib ./symbol-test 2>&1)
+ result=$(LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib $TEST_DIRECTORY/symbol-test 2>&1)
}
output="A Xapian exception occurred opening database: Couldn't stat 'fakedb/.notmuch/xapian'
caught No chert database found at path \`./nonexistant'"
-g++ -o symbol-test -I$TEST_DIRECTORY/../lib $TEST_DIRECTORY/symbol-test.cc -L$TEST_DIRECTORY/../lib -lnotmuch -lxapian
mkdir -p fakedb/.notmuch
+
test_expect_success 'running test' run_test
+
test_begin_subtest 'checking output'
test_expect_equal "$result" "$output"
+
+test_begin_subtest 'comparing existing to exported symbols'
+objdump -t $TEST_DIRECTORY/../lib/*.o | awk '$4 == ".text" && $6 ~ "^notmuch" {print $6}' | sort | uniq > ACTUAL
+sed -n 's/[[:blank:]]*\(notmuch_[^;]*\);/\1/p' $TEST_DIRECTORY/../notmuch.sym | sort | uniq > EXPORTED
+test_expect_equal_file EXPORTED ACTUAL
+
test_done
diff --git a/test/symbol-test.cc b/test/symbol-test.cc
index 1de06eae..1548ca40 100644
--- a/test/symbol-test.cc
+++ b/test/symbol-test.cc
@@ -1,17 +1,17 @@
#include <stdio.h>
#include <xapian.h>
#include <notmuch.h>
-main (int argc, char **argv){
- notmuch_database_t *notmuch
- = notmuch_database_open ("fakedb",
- NOTMUCH_DATABASE_MODE_READ_ONLY);
- try{
- (void)new Xapian::WritableDatabase ("./nonexistant", Xapian::DB_OPEN);
+int main() {
+ (void) notmuch_database_open("fakedb", NOTMUCH_DATABASE_MODE_READ_ONLY);
+
+ try {
+ (void) new Xapian::WritableDatabase("./nonexistant", Xapian::DB_OPEN);
} catch (const Xapian::Error &error) {
- printf("caught %s\n",error.get_msg().c_str());
+ printf("caught %s\n", error.get_msg().c_str());
return 0;
}
+
return 1;
}
diff --git a/test/test-lib.el b/test/test-lib.el
index 97ae5938..3b817c37 100644
--- a/test/test-lib.el
+++ b/test/test-lib.el
@@ -61,3 +61,18 @@ running, quit if it terminated."
(if (not (process-attributes pid))
(kill-emacs)
(run-at-time "1 min" nil 'orphan-watchdog pid)))
+
+(defun hook-counter (hook)
+ "Count how many times a hook is called. Increments
+`hook'-counter variable value if it is bound, otherwise does
+nothing."
+ (let ((counter (intern (concat (symbol-name hook) "-counter"))))
+ (if (boundp counter)
+ (set counter (1+ (symbol-value counter))))))
+
+(defun add-hook-counter (hook)
+ "Add hook to count how many times `hook' is called."
+ (add-hook hook (apply-partially 'hook-counter hook)))
+
+(add-hook-counter 'notmuch-hello-mode-hook)
+(add-hook-counter 'notmuch-hello-refresh-hook)
diff --git a/test/test-lib.sh b/test/test-lib.sh
index cf309f94..b5e346c0 100755..100644
--- a/test/test-lib.sh
+++ b/test/test-lib.sh
@@ -116,6 +116,16 @@ do
esac
done
+if test -n "$debug"; then
+ print_subtest () {
+ printf " %-4s" "[$((test_count - 1))]"
+ }
+else
+ print_subtest () {
+ true
+ }
+fi
+
if test -n "$color"; then
say_color () {
(
@@ -132,6 +142,7 @@ if test -n "$color"; then
printf " "
printf "$@"
tput sgr0
+ print_subtest
)
}
else
@@ -140,6 +151,7 @@ else
shift
printf " "
printf "$@"
+ print_subtest
}
fi
@@ -398,6 +410,8 @@ emacs_deliver_message ()
(insert \"${body}\")
$@
(message-send-and-exit))"
+ # opportunistically quit smtp-dummy in case above fails.
+ { echo QUIT > /dev/tcp/localhost/25025; } 2>/dev/null
wait ${smtp_dummy_pid}
notmuch new >/dev/null
}
@@ -427,7 +441,7 @@ test_begin_subtest ()
error "bug in test script: Missing test_expect_equal in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}"
fi
test_subtest_name="$1"
- test_subtest_known_broken_=
+ test_reset_state_
# Remember stdout and stderr file descriptors and redirect test
# output to the previously prepared file descriptors 3 and 4 (see
# below)
@@ -546,6 +560,33 @@ test_have_prereq () {
esac
}
+# declare prerequisite for the given external binary
+test_declare_external_prereq () {
+ binary="$1"
+ test "$#" = 2 && name=$2 || name="$binary(1)"
+
+ hash $binary 2>/dev/null || eval "
+ test_missing_external_prereq_${binary}_=t
+$binary () {
+ echo -n \"\$test_subtest_missing_external_prereqs_ \" | grep -qe \" $name \" ||
+ test_subtest_missing_external_prereqs_=\"\$test_subtest_missing_external_prereqs_ $name\"
+ false
+}"
+}
+
+# Explicitly require external prerequisite. Useful when binary is
+# called indirectly (e.g. from emacs).
+# Returns success if dependency is available, failure otherwise.
+test_require_external_prereq () {
+ binary="$1"
+ if [ "$(eval echo -n \$test_missing_external_prereq_${binary}_)" = t ]; then
+ # dependency is missing, call the replacement function to note it
+ eval "$binary"
+ else
+ true
+ fi
+}
+
# You are not expected to call test_ok_ and test_failure_ directly, use
# the text_expect_* functions instead.
@@ -579,14 +620,14 @@ test_failure_message_ () {
}
test_known_broken_ok_ () {
- test_subtest_known_broken_=
+ test_reset_state_
test_fixed=$(($test_fixed+1))
say_color pass "%-6s" "FIXED"
echo " $@"
}
test_known_broken_failure_ () {
- test_subtest_known_broken_=
+ test_reset_state_
test_broken=$(($test_broken+1))
test_failure_message_ "BROKEN" "$@"
return 1
@@ -622,18 +663,32 @@ test_skip () {
fi
case "$to_skip" in
t)
- test_subtest_known_broken_=
- say_color skip >&3 "skipping test: $@"
- say_color skip "%-6s" "SKIP"
- echo " $1"
- : true
+ test_report_skip_ "$@"
;;
*)
- false
+ test_check_missing_external_prereqs_ "$@"
;;
esac
}
+test_check_missing_external_prereqs_ () {
+ if test -n "$test_subtest_missing_external_prereqs_"; then
+ say_color skip >&3 "missing prerequisites:"
+ echo "$test_subtest_missing_external_prereqs_" >&3
+ test_report_skip_ "$@"
+ else
+ false
+ fi
+}
+
+test_report_skip_ () {
+ test_reset_state_
+ say_color skip >&3 "skipping test:"
+ echo " $@" >&3
+ say_color skip "%-6s" "SKIP"
+ echo " $1"
+}
+
test_subtest_known_broken () {
test_subtest_known_broken_=t
}
@@ -642,10 +697,14 @@ test_expect_success () {
test "$#" = 3 && { prereq=$1; shift; } || prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ test_reset_state_
if ! test_skip "$@"
then
test_run_ "$2"
- if [ "$?" = 0 -a "$eval_ret" = 0 ]
+ run_ret="$?"
+ # test_run_ may update missing external prerequisites
+ test_check_missing_external_prereqs_ "$@" ||
+ if [ "$run_ret" = 0 -a "$eval_ret" = 0 ]
then
test_ok_ "$1"
else
@@ -658,10 +717,14 @@ test_expect_code () {
test "$#" = 4 && { prereq=$1; shift; } || prereq=
test "$#" = 3 ||
error "bug in the test script: not 3 or 4 parameters to test-expect-code"
+ test_reset_state_
if ! test_skip "$@"
then
test_run_ "$3"
- if [ "$?" = 0 -a "$eval_ret" = "$1" ]
+ run_ret="$?"
+ # test_run_ may update missing external prerequisites,
+ test_check_missing_external_prereqs_ "$@" ||
+ if [ "$run_ret" = 0 -a "$eval_ret" = "$1" ]
then
test_ok_ "$2"
else
@@ -684,6 +747,7 @@ test_external () {
error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
descr="$1"
shift
+ test_reset_state_
if ! test_skip "$descr" "$@"
then
# Announce the script to reduce confusion about the
@@ -842,17 +906,22 @@ EOF
}
test_emacs () {
+ # test dependencies beforehand to avoid the waiting loop below
+ test_require_external_prereq emacs || return
+ test_require_external_prereq emacsclient || return
+
if [ -z "$EMACS_SERVER" ]; then
- EMACS_SERVER="notmuch-test-suite-$$"
+ server_name="notmuch-test-suite-$$"
# start a detached session with an emacs server
# user's TERM is given to dtach which assumes a minimally
# VT100-compatible terminal -- and emacs inherits that
TERM=$ORIGINAL_TERM dtach -n "$TEST_TMPDIR/emacs-dtach-socket.$$" \
sh -c "stty rows 24 cols 80; exec '$TMP_DIRECTORY/run_emacs' \
--no-window-system \
- --eval '(setq server-name \"$EMACS_SERVER\")' \
+ --eval '(setq server-name \"$server_name\")' \
--eval '(server-start)' \
--eval '(orphan-watchdog $$)'" || return
+ EMACS_SERVER="$server_name"
# wait until the emacs server is up
until test_emacs '()' 2>/dev/null; do
sleep 1
@@ -862,6 +931,67 @@ test_emacs () {
emacsclient --socket-name="$EMACS_SERVER" --eval "(progn $@)"
}
+test_python() {
+ export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib
+ export PYTHONPATH=$TEST_DIRECTORY/../bindings/python
+
+ # Some distros (e.g. Arch Linux) ship Python 2.* as /usr/bin/python2,
+ # most others as /usr/bin/python. So first try python2, and fallback to
+ # python if python2 doesn't exist.
+ cmd=python2
+ [[ "$test_missing_external_prereq_python2_" = t ]] && cmd=python
+
+ (echo "import sys; _orig_stdout=sys.stdout; sys.stdout=open('OUTPUT', 'w')"; cat) \
+ | $cmd -
+}
+
+# Creates a script that counts how much time it is executed and calls
+# notmuch. $notmuch_counter_command is set to the path to the
+# generated script. Use notmuch_counter_value() function to get the
+# current counter value.
+notmuch_counter_reset () {
+ notmuch_counter_command="$TMP_DIRECTORY/notmuch_counter"
+ if [ ! -x "$notmuch_counter_command" ]; then
+ notmuch_counter_state_path="$TMP_DIRECTORY/notmuch_counter.state"
+ cat >"$notmuch_counter_command" <<EOF || return
+#!/bin/sh
+
+read count < "$notmuch_counter_state_path"
+echo \$((count + 1)) > "$notmuch_counter_state_path"
+
+exec notmuch "\$@"
+EOF
+ chmod +x "$notmuch_counter_command" || return
+ fi
+
+ echo 0 > "$notmuch_counter_state_path"
+}
+
+# Returns the current notmuch counter value.
+notmuch_counter_value () {
+ if [ -r "$notmuch_counter_state_path" ]; then
+ read count < "$notmuch_counter_state_path"
+ else
+ count=0
+ fi
+ echo $count
+}
+
+test_reset_state_ () {
+ test -z "$test_init_done_" && test_init_
+
+ test_subtest_known_broken_=
+ test_subtest_missing_external_prereqs_=
+}
+
+# called once before the first subtest
+test_init_ () {
+ test_init_done_=t
+
+ # skip all tests if there were external prerequisites missing during init
+ test_check_missing_external_prereqs_ "all tests in $this_test" && test_done
+}
+
find_notmuch_path ()
{
@@ -1069,3 +1199,12 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON
# test whether the filesystem supports symbolic links
ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
rm -f y
+
+# declare prerequisites for external binaries used in tests
+test_declare_external_prereq dtach
+test_declare_external_prereq emacs
+test_declare_external_prereq emacsclient
+test_declare_external_prereq gdb
+test_declare_external_prereq gpg
+test_declare_external_prereq python
+test_declare_external_prereq python2
diff --git a/util/Makefile.local b/util/Makefile.local
index 2ff42b3d..26e4c3f3 100644
--- a/util/Makefile.local
+++ b/util/Makefile.local
@@ -9,3 +9,5 @@ libutil_modules := $(libutil_c_srcs:.c=.o)
$(dir)/libutil.a: $(libutil_modules)
$(call quiet,AR) rcs $@ $^
+
+CLEAN := $(CLEAN) $(libutil_modules) $(dir)/libutil.a
diff --git a/version b/version
index 5eef0f10..51176c7c 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.10.2
+0.11