GMime is available from https://github.com/jstedfast/gmime
+ Sfsexp
+ ------
+
+ sfsexp is the "small fast s-expression" library. Notmuch
+ optionally use it to provide a second query parser.
+
+ sfsexp is available from https://github.com/mjsottile/sfsexp.
+ In Debian Bookworm and later, install libsexp-dev.
+
Talloc
------
Talloc is a memory-pool allocator used by Notmuch.
+Notmuch 0.34.2 (2021-12-09)
+===========================
+
+Library
+-------
+
+Fix a bug that wrongly resolved conflict between the `database_path`
+parameter to `notmuch_database_open_with_config` and configuration
+item `database.path` in favour of the latter.
+
+Python Bindings (notmuch2)
+--------------------------
+
+When building the documentation for the `notmuch2` python module,
+import from the built module, not a system wide installed one.
+
+The notmuch2.Database constructor now uses the library function
+`notmuch_database_open_with_config` to support the same configuration
+and database location options as the library does.
+
+Fix some unprintable exception objects.
+
+Notmuch 0.34.1 (2021-11-03)
+===========================
+
+Library
+-------
+
+Fix for deallocation and nulling of output parameter for
+notmuch_database_{open_with,create_with,load}_config when errors
+occur. This change fixes a potential use-after-free bug that has been
+present since 0.32. This release also improves the documentation of
+status returns for the same 3 functions.
+
+Notmuch 0.34 (2021-10-20)
+=========================
+
+General
+-------
+
+An optional new s-expression based query parser is available if
+notmuch is built with the `sfsexp` library. See
+notmuch-sexp-queries(7) for syntax, and use `notmuch config get
+built_with.sexpr_query` to check if notmuch is compiled with
+s-expression query support.
+
+CLI
+---
+
+Support multiple `Delivered-To` headers in notmuch-reply(1).
+
+Emacs
+-----
+
+Functions are now allowed in `notmuch-search-result-format`.
+
+Improvements to unthreaded view on large threads.
+
+Tolerate bad/missing working directory for most commands.
+
+Allow customization of tree drawing symbols in notmuch-tree mode.
+
+Notmuch 0.33.2 (2021-09-30)
+===========================
+
+Tests
+-----
+
+Improve reliability of T355-smime by changing gpgsm initialization.
+
Notmuch 0.33.1 (2021-09-10)
===========================
Library
-------
-Correct documentatation about transactions.
+Correct documentation about transactions.
Add a configurable automatic commit of transactions. See
`database.autocommit` in notmuch-config(1).
session has been fixed.
As always, the canonical source of API documentation is
-`lib/notmuch.h`, or the doxygen formatted documentation in `notmuch(3)`
+`lib/notmuch.h`, or the doxygen formatted documentation in `notmuch(3)`.
CLI
---
Build
-----
-Portability update for T360-symbol-hiding
+Portability update for T360-symbol-hiding.
Library
-------
-Fix for memory error in notmuch_database_get_config_list
+Fix for memory error in notmuch_database_get_config_list.
Notmuch 0.31.2 (2020-11-08)
===========================
----------------------
`notmuch show` now supports --body=false and --include-html with
---format=text
+--format=text.
Fix several performance problems with `notmuch reindex`.
NOTMUCH_STATUS_NO_CONFIG,
NOTMUCH_STATUS_NO_DATABASE,
NOTMUCH_STATUS_DATABASE_EXISTS,
+ NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
NOTMUCH_STATUS_LAST_STATUS
} notmuch_status_t;
typedef enum {
notmuch_status_to_string (notmuch_status_t status);
notmuch_status_t
- notmuch_database_create_verbose (const char *path,
- notmuch_database_t **database,
- char **error_message);
- notmuch_status_t
- notmuch_database_create (const char *path, notmuch_database_t **database);
- notmuch_status_t
- notmuch_database_open_verbose (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database,
- char **error_message);
- notmuch_status_t
- notmuch_database_open (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database);
+ notmuch_database_create_with_config (const char *database_path,
+ const char *config_path,
+ const char *profile,
+ notmuch_database_t **database,
+ char **error_message);
+ notmuch_status_t
+ notmuch_database_open_with_config (const char *database_path,
+ notmuch_database_mode_t mode,
+ const char *config_path,
+ const char *profile,
+ notmuch_database_t **database,
+ char **error_message);
notmuch_status_t
notmuch_database_close (notmuch_database_t *database);
notmuch_status_t
READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY
READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE
+class ConfigFile(enum.Enum):
+ EMPTY = b''
+ SEARCH = capi.ffi.NULL
class QuerySortOrder(enum.Enum):
OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST
:cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``,
``FLAG``, ``FALSE`` or ``ALL``. See the query documentation
for details.
+ :cvar CONFIG: Control loading of config file. Enumeration of
+ ``EMPTY`` (don't load a config file), and ``SEARCH`` (search as
+ in :ref:`config_search`)
:cvar AddedMessage: A namedtuple ``(msg, dup)`` used by
:meth:`add` as return value.
:cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items.
still open.
:param path: The directory of where the database is stored. If
- ``None`` the location will be read from the user's
- configuration file, respecting the ``NOTMUCH_CONFIG``
- environment variable if set.
+ ``None`` the location will be searched according to
+ :ref:`database`
:type path: str, bytes, os.PathLike or pathlib.Path
:param mode: The mode to open the database in. One of
:attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`. For
:attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`.
:type mode: :attr:`MODE` or str.
+ :param config: Where to load the configuration from, if any.
+ :type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
:raises KeyError: if an unknown mode string is used.
:raises OSError: or subclasses if the configuration file can not
be opened.
MODE = Mode
SORT = QuerySortOrder
EXCLUDE = QueryExclude
+ CONFIG = ConfigFile
AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
_db_p = base.MemoryPointer()
STR_MODE_MAP = {
'rw': MODE.READ_WRITE,
}
- def __init__(self, path=None, mode=MODE.READ_ONLY):
+ @staticmethod
+ def _cfg_path_encode(path):
+ if isinstance(path,ConfigFile):
+ path = path.value
+ elif path is None:
+ path = capi.ffi.NULL
+ elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
+ path = bytes(path)
+ else:
+ path = os.fsencode(path)
+ return path
+
+ @staticmethod
+ def _db_path_encode(path):
+ if path is None:
+ path = capi.ffi.NULL
+ elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
+ path = bytes(path)
+ else:
+ path = os.fsencode(path)
+ return path
+
+ def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.EMPTY):
if isinstance(mode, str):
mode = self.STR_MODE_MAP[mode]
self.mode = mode
- if path is None:
- path = self.default_path()
- if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
- path = bytes(path)
+
db_pp = capi.ffi.new('notmuch_database_t **')
cmsg = capi.ffi.new('char**')
- ret = capi.lib.notmuch_database_open_verbose(os.fsencode(path),
- mode.value, db_pp, cmsg)
+ ret = capi.lib.notmuch_database_open_with_config(self._db_path_encode(path),
+ mode.value,
+ self._cfg_path_encode(config),
+ capi.ffi.NULL,
+ db_pp, cmsg)
if cmsg[0]:
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
capi.lib.free(cmsg[0])
self.closed = False
@classmethod
- def create(cls, path=None):
+ def create(cls, path=None, config=ConfigFile.EMPTY):
"""Create and open database in READ_WRITE mode.
This is creates a new notmuch database and returns an opened
instance in :attr:`MODE.READ_WRITE` mode.
- :param path: The directory of where the database is stored. If
- ``None`` the location will be read from the user's
- configuration file, respecting the ``NOTMUCH_CONFIG``
- environment variable if set.
+ :param path: The directory of where the database is stored.
+ If ``None`` the location will be read searched by the
+ notmuch library (see notmuch(3)::notmuch_open_with_config).
:type path: str, bytes or os.PathLike
+ :param config: The pathname of the notmuch configuration file.
+ :type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
+
:raises OSError: or subclasses if the configuration file can not
be opened.
:raises configparser.Error: or subclasses if the configuration
:returns: The newly created instance.
"""
- if path is None:
- path = cls.default_path()
- if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
- path = bytes(path)
+
db_pp = capi.ffi.new('notmuch_database_t **')
cmsg = capi.ffi.new('char**')
- ret = capi.lib.notmuch_database_create_verbose(os.fsencode(path),
- db_pp, cmsg)
+ ret = capi.lib.notmuch_database_create_with_config(cls._db_path_encode(path),
+ cls._cfg_path_encode(config),
+ capi.ffi.NULL,
+ db_pp, cmsg)
if cmsg[0]:
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
capi.lib.free(cmsg[0])
ret = capi.lib.notmuch_database_destroy(db_pp[0])
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
raise errors.NotmuchError(ret)
- return cls(path, cls.MODE.READ_WRITE)
+ return cls(path, cls.MODE.READ_WRITE, config=config)
@staticmethod
def default_path(cfg_path=None):
:param cfg_path: The pathname of the notmuch configuration file.
If not specified tries to use the pathname provided in the
- :env:`NOTMUCH_CONFIG` environment variable and falls back
- to :file:`~/.notmuch-config.
+ :envvar:`NOTMUCH_CONFIG` environment variable and falls back
+ to :file:`~/.notmuch-config`.
:type cfg_path: str, bytes, os.PathLike or pathlib.Path.
:returns: The path of the database, which does not necessarily
be opened.
:raises configparser.Error: or subclasses if the configuration
file can not be parsed.
- :raises NotmuchError if the config file does not have the
+ :raises NotmuchError: if the config file does not have the
database.path setting.
+
+ .. deprecated:: 0.35
+ Use the ``config`` parameter to :meth:`__init__` or :meth:`__create__` instead.
"""
if not cfg_path:
cfg_path = _config_pathname()
NoDatabaseError,
capi.lib.NOTMUCH_STATUS_DATABASE_EXISTS:
DatabaseExistsError,
+ capi.lib.NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
+ QuerySyntaxError,
}
return types[status]
if self.message:
return self.message
elif self.status:
- return capi.lib.notmuch_status_to_string(self.status)
+ char_str = capi.lib.notmuch_status_to_string(self.status)
+ return capi.ffi.string(char_str).decode(errors='replace')
else:
return 'Unknown error'
class NoConfigError(NotmuchError): pass
class NoDatabaseError(NotmuchError): pass
class DatabaseExistsError(NotmuchError): pass
+class QuerySyntaxError(NotmuchError): pass
class ObjectDestroyedError(NotmuchError):
"""The object has already been destroyed and it's memory freed.
--- /dev/null
+from notmuch2 import _capi as capi
+from notmuch2 import _errors as errors
+
+def test_status_no_message():
+ exc = errors.NotmuchError(capi.lib.NOTMUCH_STATUS_PATH_ERROR)
+ assert exc.status == capi.lib.NOTMUCH_STATUS_PATH_ERROR
+ assert exc.message is None
+ assert str(exc) == 'Path supplied is illegal for this function'
# this file should be kept in sync with ../../../version
-__VERSION__ = '0.33.1'
+__VERSION__ = '0.34.2'
SOVERSION = '5'
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
- printf 'No.\nCould not make tempdir for testing session-key support.\n'
- errors=$((errors + 1))
+ printf 'No.\nCould not make tempdir for testing session-key support.\n'
+ errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _check_session_keys.c ${gmime_ldflags} -o _check_session_keys \
- && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
- && SESSION_KEY=$(GNUPGHOME=${TEMP_GPG} ./_check_session_keys) \
- && [ $SESSION_KEY = 9:0BACD64099D1468AB07C796F0C0AC4851948A658A15B34E803865E9FC635F2F5 ]
+ && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
+ && SESSION_KEY=$(GNUPGHOME=${TEMP_GPG} ./_check_session_keys) \
+ && [ $SESSION_KEY = 9:0BACD64099D1468AB07C796F0C0AC4851948A658A15B34E803865E9FC635F2F5 ]
then
- printf "OK.\n"
+ printf "OK.\n"
else
- cat <<EOF
+ cat <<EOF
No.
*** Error: Could not extract session keys from encrypted message.
Please try to rebuild your version of GMime against a more recent
version of GPGME (at least GPGME 1.8.0).
EOF
- if command -v gpgme-config >/dev/null; then
- printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)"
- else
- printf 'You do not have the GPGME development libraries installed.\n'
- fi
- errors=$((errors + 1))
+ if command -v gpgme-config >/dev/null; then
+ printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)"
+ else
+ printf 'You do not have the GPGME development libraries installed.\n'
+ fi
+ errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
- rm -rf "$TEMP_GPG"
+ rm -rf "$TEMP_GPG"
fi
# see https://github.com/jstedfast/gmime/pull/90
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
- printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n'
- errors=$((errors + 1))
+ printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n'
+ errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _check_x509_validity.c ${gmime_ldflags} -o _check_x509_validity \
- && echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \
- && echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \
- && GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt
+ && echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \
+ && echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \
+ && GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt
then
- if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then
- gmime_x509_cert_validity=1
- printf "Yes.\n"
- else
- gmime_x509_cert_validity=0
- printf "No.\n"
- if pkg-config --exists "gmime-3.0 >= 3.2.7"; then
- cat <<EOF
+ if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then
+ gmime_x509_cert_validity=1
+ printf "Yes.\n"
+ else
+ gmime_x509_cert_validity=0
+ printf "No.\n"
+ if pkg-config --exists "gmime-3.0 >= 3.2.7"; then
+ cat <<EOF
*** Error: GMime fails to calculate X.509 certificate validity, and
is later than 3.2.7, which should have fixed this issue.
Please follow up on https://github.com/jstedfast/gmime/pull/90 with
more details.
EOF
- errors=$((errors + 1))
- fi
- fi
+ errors=$((errors + 1))
+ fi
+ fi
else
- printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
- errors=$((errors + 1))
+ printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
+ errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
- rm -rf "$TEMP_GPG"
+ rm -rf "$TEMP_GPG"
fi
# see https://dev.gnupg.org/T3464
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
- printf 'No.\nCould not make tempdir for testing signature verification when decrypting with session keys.\n'
- errors=$((errors + 1))
+ printf 'No.\nCould not make tempdir for testing signature verification when decrypting with session keys.\n'
+ errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _verify_sig_with_session_key.c ${gmime_ldflags} -o _verify_sig_with_session_key \
- && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
- && rm -f ${TEMP_GPG}/private-keys-v1.d/*.key
+ && GNUPGHOME=${TEMP_GPG} gpg --batch --quiet --import < "$srcdir"/test/gnupg-secret-key.asc \
+ && rm -f ${TEMP_GPG}/private-keys-v1.d/*.key
then
- if GNUPGHOME=${TEMP_GPG} ./_verify_sig_with_session_key; then
- gmime_verify_with_session_key=1
- printf "Yes.\n"
- else
- gmime_verify_with_session_key=0
- printf "No.\n"
- cat <<EOF
+ if GNUPGHOME=${TEMP_GPG} ./_verify_sig_with_session_key; then
+ gmime_verify_with_session_key=1
+ printf "Yes.\n"
+ else
+ gmime_verify_with_session_key=0
+ printf "No.\n"
+ cat <<EOF
*** Error: GMime fails to verify signatures when decrypting with a session key.
This is most likely due to a buggy version of GPGME, which should be fixed in 1.13.2 or later.
See https://dev.gnupg.org/T3464 for more details.
EOF
- fi
+ fi
else
- printf 'No.\nFailed to set up gpg for testing signature verification while decrypting with a session key.\n'
- errors=$((errors + 1))
+ printf 'No.\nFailed to set up gpg for testing signature verification while decrypting with a session key.\n'
+ errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
- rm -rf "$TEMP_GPG"
+ rm -rf "$TEMP_GPG"
fi
else
have_gmime=0
if [ $have_python -eq 1 ]; then
printf "Checking for python3 (>= 3.5)..."
if "$python" -c 'import sys, sysconfig; assert sys.version_info >= (3,5)'; >/dev/null 2>&1; then
- printf "Yes.\n"
- have_python3=1
+ printf "Yes.\n"
+ have_python3=1
+ else
+ printf "No (will not install CFFI-based python bindings).\n"
+ fi
+fi
+
+have_python3_dev=0
+if [ $have_python3 -eq 1 ]; then
+ printf "Checking for python3 version ..."
+ python3_version=$("$python" -c 'import sysconfig; print(sysconfig.get_python_version());')
+ printf "(%s)\n" $python3_version
+
+ printf "Checking for python $python3_version development files..."
+ if pkg-config --exists "python-$python3_version"; then
+ have_python3_dev=1
+ printf "Yes.\n"
else
- printf "No (will not install CFFI-based python bindings).\n"
+ have_python3_dev=0
+ printf "No (will not install CFFI-based python bindings).\n"
fi
fi
have_python3_cffi=0
have_python3_pytest=0
-if [ $have_python3 -eq 1 ]; then
+if [ $have_python3_dev -eq 1 ]; then
printf "Checking for python3 cffi and setuptools... "
if "$python" -c 'import cffi,setuptools; cffi.FFI().verify()' >/dev/null 2>&1; then
- printf "Yes.\n"
- have_python3_cffi=1
- WITH_PYTHON_DOCS=1
+ printf "Yes.\n"
+ have_python3_cffi=1
+ WITH_PYTHON_DOCS=1
else
- WITH_PYTHON_DOCS=0
- printf "No (will not install CFFI-based python bindings).\n"
+ WITH_PYTHON_DOCS=0
+ printf "No (will not install CFFI-based python bindings).\n"
fi
rm -rf __pycache__ # cffi.FFI().verify() uses this space
conf=$(mktemp)
printf "[pytest]\nminversion=3.0\n" > $conf
if "$python" -m pytest -c $conf --version >/dev/null 2>&1; then
- printf "Yes.\n"
- have_python3_pytest=1
+ printf "Yes.\n"
+ have_python3_pytest=1
else
- printf "No (will not test CFFI-based python bindings).\n"
+ printf "No (will not test CFFI-based python bindings).\n"
fi
rm -f $conf
fi
WITH_BASH=0
fi
+printf "Checking for sfsexp... "
+if pkg-config --exists sfsexp; then
+ printf "Yes.\n"
+ have_sfsexp=1
+ sfsexp_cflags=$(pkg-config --cflags sfsexp)
+ sfsexp_ldflags=$(pkg-config --libs sfsexp)
+else
+ printf "No (will not enable s-expression queries).\n"
+ have_sfsexp=0
+ sfsexp_cflags=
+ sfsexp_ldflags=
+fi
+
if [ -z "${EMACSLISPDIR-}" ]; then
EMACSLISPDIR="\$(prefix)/share/emacs/site-lisp"
fi
if [ $WITH_EMACS = "1" ]; then
printf "Checking if emacs (>= 25) is available... "
if emacs --quick --batch --eval '(if (< emacs-major-version 25) (kill-emacs 1))' > /dev/null 2>&1; then
- printf "Yes.\n"
+ printf "Yes.\n"
else
- printf "No (disabling emacs related parts of build)\n"
- WITH_EMACS=0
+ printf "No (disabling emacs related parts of build)\n"
+ WITH_EMACS=0
fi
fi
# And if so, flags needed at compile time for valgrind macros
VALGRIND_CFLAGS = ${valgrind_cflags}
+# Whether the sfsexp library is available
+HAVE_SFSEXP = ${have_sfsexp}
+
+# And if so, flags needed at compile/link time for sfsexp
+SFSEXP_CFLAGS = ${sfsexp_cflags}
+SFSEXP_LDFLAGS = ${sfsexp_ldflags}
+
# Support for emacs
WITH_EMACS = ${WITH_EMACS}
COMMON_CONFIGURE_CFLAGS = \\
\$(GMIME_CFLAGS) \$(TALLOC_CFLAGS) \$(ZLIB_CFLAGS) \\
-DHAVE_VALGRIND=\$(HAVE_VALGRIND) \$(VALGRIND_CFLAGS) \\
+ -DHAVE_SFSEXP=\$(HAVE_SFSEXP) \$(SFSEXP_CFLAGS) \\
-DHAVE_GETLINE=\$(HAVE_GETLINE) \\
-DWITH_EMACS=\$(WITH_EMACS) \\
-DHAVE_CANONICALIZE_FILE_NAME=\$(HAVE_CANONICALIZE_FILE_NAME) \\
CONFIGURE_CXXFLAGS = \$(COMMON_CONFIGURE_CFLAGS) \$(XAPIAN_CXXFLAGS)
-CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS)
+CONFIGURE_LDFLAGS = \$(GMIME_LDFLAGS) \$(TALLOC_LDFLAGS) \$(ZLIB_LDFLAGS) \$(XAPIAN_LDFLAGS) \$(SFSEXP_LDFLAGS)
EOF
# construct the sh.config
# Is the python pytest package available?
NOTMUCH_HAVE_PYTHON3_PYTEST=${have_python3_pytest}
+# Is the sfsexp library available?
+NOTMUCH_HAVE_SFSEXP=${have_sfsexp}
+
# Platform we are run on
PLATFORM=${platform}
EOF
{
echo "# Generated by configure, run from doc/conf.py"
if [ $WITH_EMACS = "1" ]; then
- echo "tags.add('WITH_EMACS')"
+ echo "tags.add('WITH_EMACS')"
fi
if [ $WITH_PYTHON_DOCS = "1" ]; then
- echo "tags.add('WITH_PYTHON')"
+ echo "tags.add('WITH_PYTHON')"
fi
printf "rsti_dir = '%s'\n" "$(cd emacs && pwd -P)"
} > sphinx.config
+notmuch (0.34.2-1) unstable; urgency=medium
+
+ * New upstream bugfix with release, with fixes database location in
+ library and notmuch2 python module.
+ * Build only against the default version of python, to avoid including
+ multiple .abi3.so files in python3-notmuch2
+
+ -- David Bremner <bremner@debian.org> Fri, 10 Dec 2021 09:35:43 -0400
+
+notmuch (0.34.1-1) unstable; urgency=medium
+
+ * New upstream bugfix release. Fixes a memory deallocation error in
+ libnotmuch.
+
+ -- David Bremner <bremner@debian.org> Wed, 03 Nov 2021 10:20:33 -0300
+
+notmuch (0.34-1) unstable; urgency=medium
+
+ * New upstream release
+ * Adds s-expression based query parser (man notmuch-sexp-queries).
+ * Bug fix: "notmuch breaks on directory removal", thanks to Joerg
+ Jaspert (Closes: #922536).
+ * Respect notmuch-show-text/html-blocked-images for renderer w3m
+ (Closes: #934082).
+ * Bug fix: "add an option to change the database path", thanks to
+ Michael Gold (Closes: #887041) (actually fixed in 0.32)
+
+ -- David Bremner <bremner@debian.org> Wed, 20 Oct 2021 11:15:23 -0300
+
+notmuch (0.34~rc0-1) experimental; urgency=medium
+
+ * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Fri, 15 Oct 2021 08:50:57 -0300
+
+notmuch (0.33.2-1) unstable; urgency=medium
+
+ * Upstream fix for flaky/hanging tests in T355-smime
+
+ -- David Bremner <bremner@debian.org> Thu, 30 Sep 2021 08:27:10 -0300
+
notmuch (0.33.1-1~bpo11+1) bullseye-backports; urgency=medium
* Rebuild for bullseye-backports.
gnupg <!nocheck>,
gpgsm <!nocheck>,
libgmime-3.0-dev (>= 3.0.3~),
- libpython3-all-dev,
+ libpython3-dev,
+ libsexp-dev,
libtalloc-dev,
libxapian-dev,
libz-dev,
pkg-config,
- python3-all (>= 3.1.2-7~),
+ python3,
python3-cffi,
python3-pytest,
python3-pytest-cov,
notmuch_query_count_threads@Base 0.10~rc1
notmuch_query_count_threads_st@Base 0.21~rc1
notmuch_query_create@Base 0.3
+ notmuch_query_create_with_syntax@Base 0.34~rc0
notmuch_query_destroy@Base 0.3
notmuch_query_get_database@Base 0.21~rc1
notmuch_query_get_query_string@Base 0.4
usr/share/man/man5/notmuch-hooks.5.gz
usr/share/man/man7/notmuch-properties.7.gz
usr/share/man/man7/notmuch-search-terms.7.gz
+usr/share/man/man7/notmuch-sexp-queries.7.gz
--- /dev/null
+#!/bin/sh
+
+# Usage suggestion:
+# git rebase -i --exec devel/check-notmuch-commit origin/master
+
+set -e
+
+quick=0
+case "$1" in
+ -q|-Q|--quick)
+ quick=1
+ ;;
+esac
+
+if [ $quick = 0 ]; then
+ make test
+fi
+
+unset uconf
+for file in $(git diff --name-only --diff-filter=AM HEAD^); do
+ case $file in
+ *.c|*.h|*.cc|*.hh)
+ uncrustify --replace -c "${uconf=$(dirname "$0")/uncrustify.cfg}" "$file"
+ ;;
+ *.el)
+ emacs -Q --batch "$file" --eval '(indent-region (point-min) (point-max) nil)' -f save-buffer
+ ;;
+ esac
+done
+
+git diff --quiet
# You can set these variables from the command line.
SPHINXOPTS := -q
-SPHINXBUILD = sphinx-build
+SPHINXBUILD = env LD_LIBRARY_PATH=${NOTMUCH_BUILDDIR}/lib sphinx-build
DOCBUILDDIR := $(dir)/_build
# Internal variables.
INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
-.PHONY: sphinx-html sphinx-texinfo sphinx-info
+.PHONY: sphinx-html sphinx-texinfo sphinx-info doc-prereqs
.PHONY: install-man build-man apidocs install-apidocs
$(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.texi.stamp : docstring.stamp
endif
+ifeq ($(HAVE_PYTHON3_CFFI),1)
+doc-prereqs: python-cffi-bindings
+endif
+
sphinx-html: $(DOCBUILDDIR)/.html.stamp
-$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES)
+$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) doc-prereqs
$(SPHINXBUILD) -b html -d $(DOCBUILDDIR)/html_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html
touch $@
sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp
-$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES)
+$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) doc-prereqs
$(SPHINXBUILD) -b texinfo -d $(DOCBUILDDIR)/texinfo_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo
touch $@
version=infile.read().replace('\n','')
# for autodoc
-sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'notmuch2'))
+sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'build', 'stage'))
# read generated config
for pathdir in ['.', '..']:
u'syntax for notmuch queries',
[notmuch_authors], 7),
+ ('man7/notmuch-sexp-queries', 'notmuch-sexp-queries',
+ u's-expression syntax for notmuch queries',
+ [notmuch_authors], 7),
+
('man1/notmuch-show', 'notmuch-show',
u'show messages matching the given search terms',
[notmuch_authors], 1),
man1/notmuch-restore
man1/notmuch-search
man7/notmuch-search-terms
+ man7/notmuch-sexp-queries
man1/notmuch-show
man1/notmuch-tag
python-bindings
:any:`notmuch-search-terms(7)` for more information about named
queries.
+**squery.<name>**
+ Expansion for named query called <name>, using s-expression syntax. See
+ :any:`notmuch-sexp-queries(7)` for more information about s-expression
+ queries.
+
FILES
=====
+.. _config_search:
+
CONFIGURATION
-------------
--- /dev/null
+.. _notmuch-sexp-queries(7):
+
+====================
+notmuch-sexp-queries
+====================
+
+SYNOPSIS
+========
+
+**notmuch** *subcommand* ``--query=sexp`` [option ...] ``--`` '(and (to santa) (date december))'
+
+DESCRIPTION
+===========
+
+Notmuch supports an alternative query syntax based on `S-expressions
+<https://en.wikipedia.org/wiki/S-expression>`_ . It can be selected
+with the command line ``--query=sexp`` or with the appropriate option
+to the library function :c:func:`notmuch_query_create_with_syntax`.
+Support for this syntax is currently optional, you can test if your
+build of notmuch supports it with
+
+::
+
+ $ notmuch config get built_with.sexpr_query
+
+
+S-EXPRESSIONS
+-------------
+
+An *s-expression* is either an atom, or list of whitespace delimited
+s-expressions inside parentheses. Atoms are either
+
+*basic value*
+ A basic value is an unquoted string containing no whitespace, double quotes, or
+ parentheses.
+
+*quoted string*
+ Double quotes (") delimit strings possibly containing whitespace
+ or parentheses. These can contain double quote characters by
+ escaping with backslash. E.g. ``"this is a quote \""``.
+
+S-EXPRESSION QUERIES
+--------------------
+
+An s-expression query is either an atom, the empty list, or a
+*compound query* consisting of a prefix atom (first element) defining
+a *field*, *logical operation*, or *modifier*, and 0 or more
+subqueries.
+
+``*``
+ "*" matches any non-empty string in the current field.
+
+``()``
+ The empty list matches all messages
+
+*term*
+
+ Match all messages containing *term*, possibly after stemming or
+ phrase splitting. For discussion of stemming in notmuch see
+ :any:`notmuch-search-terms(7)`. Stemming only applies to unquoted
+ terms (basic values) in s-expression queries. For information on
+ phrase splitting see :any:`fields`.
+
+``(`` *field* |q1| |q2| ... |qn| ``)``
+ Restrict the queries |q1| to |qn| to *field*, and combine with *and*
+ (for most fields) or *or*. See :any:`fields` for more information.
+
+``(`` *operator* |q1| |q2| ... |qn| ``)``
+ Combine queries |q1| to |qn|. Currently supported operators are
+ ``and``, ``or``, and ``not``. ``(not`` |q1| ... |qn| ``)`` is equivalent
+ to ``(and (not`` |q1| ``) ... (not`` |qn| ``))``.
+
+``(`` *modifier* |q1| |q2| ... |qn| ``)``
+ Combine queries |q1| to |qn|, and reinterpret the result (e.g. as a regular expression).
+ See :any:`modifiers` for more information.
+
+``(macro (`` |p1| ... |pn| ``) body)``
+ Define saved query with parameter substitution. The syntax is
+ recognized only in saved s-expression queries (see ``squery.*`` in
+ :any:`notmuch-config(1)`). Parameter names in ``body`` must be
+ prefixed with ``,`` to be expanded (see :any:`macro_examples`).
+ Macros may refer to other macros, but only to their own
+ parameters [#macro-details]_.
+
+.. _fields:
+
+FIELDS
+``````
+
+*Fields* [#aka-pref]_
+correspond to attributes of mail messages. Some are inherent (and
+immutable) like ``subject``, while others ``tag`` and ``property`` are
+settable by the user. Each concrete field in
+:any:`the table below <field-table>`
+is discussed further under "Search prefixes" in
+:any:`notmuch-search-terms(7)`. The row *user* refers to user defined
+fields, described in :any:`notmuch-config(1)`.
+
+Most fields are either *phrase fields* [#aka-prob]_ (which match
+sequences of words), or *term fields* [#aka-bool]_ (which match exact
+strings). *Phrase splitting* breaks the term (basic value or quoted
+string) into words, ignore punctuation. Phrase splitting is applied to
+terms in phrase (probabilistic) fields. Both phrase splitting and
+stemming apply only in phrase fields.
+
+Each term or phrase field has an associated combining operator
+(``and`` or ``or``) used to combine the queries from each element of
+the tail of the list. This is generally ``or`` for those fields where
+a message has one such attribute, and ``and`` otherwise.
+
+Term or phrase fields can contain arbitrarily complex queries made up
+from terms, operators, and modifiers, but not other fields.
+
+.. _field-table:
+
+.. table:: Fields with supported modifiers
+
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | field | combine | type | expand | wildcard | regex |
+ +============+===========+===========+===========+===========+==========+
+ | *none* | and | | no | yes | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | *user* | and | phrase | no | yes | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | attachment | and | phrase | yes | yes | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | body | and | phrase | no | no | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | date | | range | no | no | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | folder | or | phrase | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | from | and | phrase | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | id | or | term | no | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | is | and | term | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | lastmod | | range | no | no | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | mid | or | term | no | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | mimetype | or | phrase | yes | yes | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | path | or | term | no | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | property | and | term | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | subject | and | phrase | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | tag | and | term | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | thread | or | term | yes | yes | yes |
+ +------------+-----------+-----------+-----------+-----------+----------+
+ | to | and | phrase | yes | yes | no |
+ +------------+-----------+-----------+-----------+-----------+----------+
+
+.. _modifiers:
+
+MODIFIERS
+`````````
+
+*Modifiers* refer to any prefixes (first elements of compound queries)
+that are neither operators nor fields.
+
+``(infix`` *atom* ``)``
+ Interpret *atom* as an infix notmuch query (see
+ :any:`notmuch-search-terms(7)`). Not supported inside fields.
+
+``(matching`` |q1| |q2| ... |qn| ``)`` ``(of`` |q1| |q2| ... |qn| ``)``
+ Match all messages have the same values of the current field as
+ those matching all of |q1| ... |qn|. Supported in most term [#not-path]_ or
+ phrase fields. Most commonly used in the ``thread`` field.
+
+``(query`` *atom* ``)``
+ Expand to the saved query named by *atom*. See
+ :any:`notmuch-config(1)` for more. Note that the saved query must
+ be in infix syntax (:any:`notmuch-search-terms(7)`). Not supported
+ inside fields.
+
+``(regex`` *atom* ``)`` ``(rx`` *atom* ``)``
+ Interpret *atom* as a POSIX.2 regular expression (see
+ :manpage:`regex(7)`). This applies in term fields and a subset [#not-phrase]_ of
+ phrase fields (see :any:`field-table`).
+
+``(starts-with`` *subword* ``)``
+ Matches any term starting with *subword*. This applies in either
+ phrase or term :any:`fields <fields>`, or outside of fields [#not-body]_. Note that
+ a ``starts-with`` query cannot be part of a phrase. The
+ atom ``*`` is a synonym for ``(starts-with "")``.
+
+EXAMPLES
+========
+
+``Wizard``
+ Match all messages containing the word "wizard", ignoring case.
+
+``added``
+ Match all messages containing "added", but also those containing "add", "additional",
+ "Additional", "adds", etc... via stemming.
+
+``(and Bob Marley)``
+ Match messages containing words "Bob" and "Marley", or their stems
+ The words need not be adjacent.
+
+``(not Bob Marley)``
+ Match messages containing neither "Bob" nor "Marley", nor their stems,
+
+``"quick fox"`` ``quick-fox`` ``quick@fox``
+ Match the *phrase* "quick" followed by "fox" in phrase fields (or
+ outside a field). Match the literal string in a term field.
+
+``(folder (of (id 1234@invalid)))``
+ Match any message in the same folder as the one with Message-Id "1234@invalid"
+
+``(id 1234@invalid blah@test)``
+ Matches Message-Id "1234@invalid" *or* Message-Id "blah@test"
+
+``(and (infix "date:2009-11-18..2009-11-18") (tag unread))``
+ Match messages in the given date range with tag unread.
+
+``(starts-with prelim)``
+ Match any words starting with "prelim".
+
+``(subject quick "brown fox")``
+ Match messages whose subject contains "quick" (anywhere, stemmed) and
+ the phrase "brown fox".
+
+``(subject (starts-with prelim))``
+ Matches any word starting with "prelim", inside a message subject.
+
+``(subject (starts-wih quick) "brown fox")``
+ Match messages whose subject contains "quick brown fox", but also
+ "brown fox quicksand".
+
+``(thread (of (id 1234@invalid)))``
+ Match any message in the same thread as the one with Message-Id "1234@invalid"
+
+``(thread (matching (from bob@example.com) (to bob@example.com)))``
+ Match any (messages in) a thread containing a message from
+ "bob@example.com" and a (possibly distinct) message to "bob at
+ example.com")
+
+``(to (or bob@example.com mallory@example.org))`` ``(or (to bob@example.com) (to mallory@example.org))``
+ Match in the "To" or "Cc" headers, "bob@example.com",
+ "mallory@example.org", and also "bob@example.com.au" since it
+ contains the adjacent triple "bob", "example", "com".
+
+``(not (to *))``
+ Match messages with an empty or invalid 'To' and 'Cc' field.
+
+``(List *)``
+ Match messages with a non-empty List-Id header, assuming
+ configuration ``index.header.List=List-Id``
+
+.. _macro_examples:
+
+MACRO EXAMPLES
+--------------
+
+A macro that takes two parameters and applies different fields to them.
+
+::
+
+ $ notmuch config set squery.TagSubject '(macro (tagname subj) (and (tag ,tagname) (subject ,subj)))'
+ $ notmuch search --query=sexp '(TagSubject inbox maildir)'
+
+Nested macros are allowed.
+
+::
+
+ $ notmuch config set squery.Inner '(macro (x) (subject ,x))'
+ $ notmuch config set squery.Outer '(macro (x y) (and (tag ,x) (Inner ,y)))'
+ $ notmuch search --query=sexp '(Outer inbox maildir)'
+
+Parameters can be re-used to reduce boilerplate. Any field, including
+user defined fields is permitted within a macro.
+
+::
+
+ $ notmuch config set squery.About '(macro (name) (or (subject ,name) (List ,name)))'
+ $ notmuch search --query=sexp '(About notmuch)'
+
+
+NOTES
+=====
+
+.. [#macro-details] Technically macros impliment lazy evaluation and
+ lexical scope. There is one top level scope
+ containing all macro definitions, but all
+ parameter definitions are local to a given macro.
+
+.. [#aka-pref] a.k.a. prefixes
+
+.. [#aka-prob] a.k.a. probabilistic prefixes
+
+.. [#aka-bool] a.k.a. boolean prefixes
+
+.. [#not-phrase] Due to the implemention of phrase fields in Xapian,
+ regex queries could only match individual words.
+
+.. [#not-body] Due the the way ``body`` is implemented in notmuch,
+ this modifier is not supported in the ``body`` field.
+
+.. [#not-path] Due to the way recursive ``path`` queries are implemented
+ in notmuch, this modifier is not supported in the
+ ``path`` field.
+
+.. |q1| replace:: :math:`q_1`
+.. |q2| replace:: :math:`q_2`
+.. |qn| replace:: :math:`q_n`
+
+.. |p1| replace:: :math:`p_1`
+.. |p2| replace:: :math:`p_2`
+.. |pn| replace:: :math:`p_n`
;; harvest if necessary.
(notmuch-address-harvest-trigger)))
(t
- (process-lines notmuch-address-command original))))
+ (notmuch--process-lines notmuch-address-command original))))
(defun notmuch-address-expand-name ()
(cond
(goto-char (point-max))
(insert (format "-- Key %s in message %s:\n"
fingerprint id))
- (call-process notmuch-crypto-gpg-program nil t t
+ (notmuch--call-process notmuch-crypto-gpg-program nil t t
"--batch" "--no-tty" "--list-keys" fingerprint))
(recenter -1))))
(with-current-buffer buffer
(goto-char (point-max))
(insert (format "--- Retrieving key %s:\n" keyid)))
- (let ((p (make-process
+ (let ((p (notmuch--make-process
:name "notmuch GPG key retrieval"
:connection-type 'pipe
:buffer buffer
(with-current-buffer buffer
(goto-char (point-max))
(insert (format "--- Retrieving key %s:\n" keyid))
- (call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid)
+ (notmuch--call-process notmuch-crypto-gpg-program nil t t "--recv-keys" keyid)
(insert "\n")
- (call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid))
+ (notmuch--call-process notmuch-crypto-gpg-program nil t t "--list-keys" keyid))
(recenter -1))
(notmuch-show-refresh-view)))))
:group 'notmuch)
(defcustom notmuch-draft-tags '("+draft")
- "List of tags changes to apply to a draft message when it is saved in the database.
+ "List of tag changes to apply when saving a draft message in the database.
Tags starting with \"+\" (or not starting with either \"+\" or
\"-\") in the list will be added, and tags starting with \"-\"
(defun notmuch-draft-resume (id)
"Resume editing of message with id ID."
;; Used by command `notmuch-show-resume-message'.
- (let* ((tags (process-lines notmuch-command "search" "--output=tags"
+ (let* ((tags (notmuch--process-lines notmuch-command "search" "--output=tags"
"--exclude=false" id))
(draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
(when (or draft
(setq buffer-read-only nil)
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ (notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
(mime-to-mml)
(goto-char (point-min))
(when (re-search-forward "^$" nil t)
Possible values are `oldest-first', `newest-first'
or nil. Nil means use the default sort order.
:search-type Specify whether to run the search in search-mode,
- tree mode or unthreaded mode. Set to 'tree to specify tree
- mode, 'unthreaded to specify unthreaded mode, and set to nil
- (or anything except tree and unthreaded) to specify search mode.
+ tree mode or unthreaded mode. Set to `tree' to
+ specify tree mode, 'unthreaded to specify
+ unthreaded mode, and set to nil (or anything
+ except tree and unthreaded) to specify search
+ mode.
Other accepted forms are a cons cell of the form (NAME . QUERY)
or a list of the form (NAME QUERY COUNT-QUERY)."
(widget-get widget :notmuch-search-oldest-first)))))
(defun notmuch-saved-search-count (search)
- (car (process-lines notmuch-command "count" search)))
+ (car (notmuch--process-lines notmuch-command "count" search)))
(defun notmuch-hello-tags-per-line (widest)
"Determine how many tags to show per line and how wide they
(or (plist-get options :filter-count)
(plist-get options :filter))))
"\n")))
- (unless (= (call-process-region (point-min) (point-max) notmuch-command
+ (unless (= (notmuch--call-process-region (point-min) (point-max) notmuch-command
t t nil "count" "--batch") 0)
(notmuch-logged-error
"notmuch count --batch failed"
(list (cons tag
(concat "tag:"
(notmuch-escape-boolean-term tag))))))
- (process-lines notmuch-command "search" "--output=tags" "*")))
+ (notmuch--process-lines notmuch-command "search" "--output=tags" "*")))
(defun notmuch-hello-insert-header ()
"Insert the default notmuch-hello header."
:help-echo "Refresh"
(notmuch-hello-nice-number
(string-to-number
- (car (process-lines notmuch-command "count")))))
+ (car (notmuch--process-lines notmuch-command "count")))))
(widget-insert " messages.\n")))
(defun notmuch-hello-insert-saved-searches ()
(start (point)))
(if is-hidden
(widget-create 'push-button
- :notify `(lambda (widget &rest _ignore)
- (setq notmuch-hello-hidden-sections
- (delete ,title notmuch-hello-hidden-sections))
- (notmuch-hello-update))
+ :notify (lambda (&rest _ignore)
+ (setq notmuch-hello-hidden-sections
+ (delete title notmuch-hello-hidden-sections))
+ (notmuch-hello-update))
"show")
(widget-create 'push-button
- :notify `(lambda (widget &rest _ignore)
- (add-to-list 'notmuch-hello-hidden-sections
- ,title)
- (notmuch-hello-update))
+ :notify (lambda (&rest _ignore)
+ (add-to-list 'notmuch-hello-hidden-sections
+ title)
+ (notmuch-hello-update))
"hide"))
(widget-insert "\n")
(unless is-hidden
(require 'notmuch-lib)
(require 'notmuch-hello)
+(declare-function notmuch-search "notmuch")
+(declare-function notmuch-tree "notmuch-tree")
+(declare-function notmuch-unthreaded "notmuch-tree")
+
;;;###autoload
(defun notmuch-jump-search ()
"Jump to a saved search by shortcut key.
(push (list key name
(cond
((eq (plist-get saved-search :search-type) 'tree)
- `(lambda () (notmuch-tree ',query)))
+ (lambda () (notmuch-tree query)))
((eq (plist-get saved-search :search-type) 'unthreaded)
- `(lambda () (notmuch-unthreaded ',query)))
+ (lambda () (notmuch-unthreaded query)))
(t
- `(lambda () (notmuch-search ',query ',oldest-first)))))
+ (lambda () (notmuch-search query oldest-first)))))
action-map)))))
(setq action-map (nreverse action-map))
(if action-map
(pcase-dolist (`(,key ,_name ,fn) action-map)
(when (= (length key) 1)
(define-key map key
- `(lambda () (interactive)
- (setq notmuch-jump--action ',fn)
- (exit-minibuffer)))))
+ (lambda ()
+ (interactive)
+ (setq notmuch-jump--action fn)
+ (exit-minibuffer)))))
;; By doing this in two passes (and checking if we already have a
;; binding) we avoid problems if the user specifies a binding which
;; is a prefix of another binding.
action-submap)
(setq action-submap (nreverse action-submap))
(define-key map keystr
- `(lambda () (interactive)
- (setq notmuch-jump--action
- ',(apply-partially #'notmuch-jump
- action-submap
- new-prompt))
- (exit-minibuffer)))))))
+ (lambda ()
+ (interactive)
+ (setq notmuch-jump--action
+ (apply-partially #'notmuch-jump
+ action-submap
+ new-prompt))
+ (exit-minibuffer)))))))
map))
(provide 'notmuch-jump)
Otherwise the output will be returned."
(with-temp-buffer
- (let ((status (apply #'call-process notmuch-command nil t nil args))
+ (let ((status (apply #'notmuch--call-process notmuch-command nil t nil args))
(output (buffer-string)))
(notmuch-check-exit-status status (cons notmuch-command args) output)
output)))
(defun notmuch-cli-sane-p ()
"Return t if the cli seems to be configured sanely."
(unless notmuch--cli-sane-p
- (let ((status (call-process notmuch-command nil nil nil
+ (let ((status (notmuch--call-process notmuch-command nil nil nil
"config" "get" "user.primary_email")))
(setq notmuch--cli-sane-p (= status 0))))
notmuch--cli-sane-p)
(message "Polling mail...")
(if (stringp notmuch-poll-script)
(unless (string-empty-p notmuch-poll-script)
- (unless (equal (call-process notmuch-poll-script nil nil) 0)
+ (unless (equal (notmuch--call-process notmuch-poll-script nil nil) 0)
(error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
(notmuch-call-notmuch-process "new"))
(message "Polling mail...done"))
;; charset is US-ASCII. RFC6657
;; complicates this somewhat.
'us-ascii)))))
- (apply #'call-process
+ (apply #'notmuch--call-process
notmuch-command nil '(t nil) nil args)
(buffer-string))))))
(when (and cache data)
;; `notmuch-logged-error' does not return.
))))
+(defmacro notmuch--apply-with-env (func &rest args)
+ `(let ((default-directory "~"))
+ (apply ,func ,@args)))
+
+(defun notmuch--process-lines (program &rest args)
+ "Wrap process-lines, binding DEFAULT-DIRECTORY to a safe
+default"
+ (notmuch--apply-with-env #'process-lines program args))
+
+(defun notmuch--make-process (&rest args)
+ "Wrap make-process, binding DEFAULT-DIRECTORY to a safe
+default"
+ (notmuch--apply-with-env #'make-process args))
+
+(defun notmuch--call-process-region (start end program
+ &optional delete buffer display
+ &rest args)
+ "Wrap call-process-region, binding DEFAULT-DIRECTORY to a safe
+default"
+ (notmuch--apply-with-env
+ #'call-process-region start end program delete buffer display args))
+
+(defun notmuch--call-process (program &optional infile destination display &rest args)
+ "Wrap call-process, binding DEFAULT-DIRECTORY to a safe default"
+ (notmuch--apply-with-env #'call-process program infile destination display args))
+
(defun notmuch-call-notmuch--helper (destination args)
"Helper for synchronous notmuch invocation commands.
(otherwise
(error "Unknown keyword argument: %s" (car args)))))
(if (null stdin-string)
- (apply #'call-process notmuch-command nil destination nil args)
+ (apply #'notmuch--call-process notmuch-command nil destination nil args)
(insert stdin-string)
- (apply #'call-process-region (point-min) (point-max)
+ (apply #'notmuch--call-process-region (point-min) (point-max)
notmuch-command t destination nil args))))
(defun notmuch-call-notmuch-process (&rest args)
(let* ((command (or (executable-find notmuch-command)
(error "Command not found: %s" notmuch-command)))
(err-buffer (generate-new-buffer " *notmuch-stderr*"))
- (proc (make-process
+ (proc (notmuch--make-process
:name name
:buffer buffer
:command (cons command args)
- a string: the value of `notmuch-fcc-dirs' is the Fcc header to
be used.
-- a list: the folder is chosen based on the From address of the
- current message using a list of regular expressions and
- corresponding folders:
+- an alist: the folder is chosen based on the From address of
+ the current message according to an alist mapping regular
+ expressions to folders or nil:
((\"Sebastian@SSpaeth.de\" . \"privat\")
(\"spaetz@sspaeth.de\" . \"OUTBOX.OSS\")
(\".*\" . \"defaultinbox\"))
- If none of the regular expressions match the From address, no
- Fcc header will be added.
+ If none of the regular expressions match the From address, or
+ if the cdr of the matching entry is nil, then no Fcc header
+ will be added.
If `notmuch-maildir-use-notmuch-insert' is set (the default) then
the header should be of the form \"folder +tag1 -tag2\" where
(const :tag "No FCC header" nil)
(string :tag "A single folder")
(repeat :tag "A folder based on the From header"
- (cons regexp (string :tag "Folder"))))
+ (cons regexp (choice (const :tag "No FCC header" nil)
+ (string :tag "Folder")))))
:require 'notmuch-fcc-initialization
:group 'notmuch-send)
;; Old style - no longer works.
(error "Invalid `notmuch-fcc-dirs' setting (old style)"))
((listp notmuch-fcc-dirs)
- (or (seq-some (let ((from (message-field-value "From")))
- (pcase-lambda (`(,regexp . ,folder))
- (and (string-match-p regexp from)
- folder)))
- notmuch-fcc-dirs)
- (progn (message "No Fcc header added.")
- nil)))
+ (if-let ((match (seq-some (let ((from (message-field-value "From")))
+ (pcase-lambda (`(,regexp . ,folder))
+ (and (string-match-p regexp from)
+ (cons t folder))))
+ notmuch-fcc-dirs)))
+ (cdr match)
+ (message "No Fcc header added.")
+ nil))
(t
(error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)")))))
(when subdir
(with-current-buffer temp-buffer
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil t nil
+ (notmuch--call-process notmuch-command nil t nil
"show" "--format=raw" id))
;; Because we process the messages in reverse order,
;; always generate a forwarded subject, then use the
"Pass the contents of the current buffer to 'muttprint'.
Optional OUTPUT allows passing a list of flags to muttprint."
- (apply #'call-process-region (point-min) (point-max)
+ (apply #'notmuch--call-process-region (point-min) (point-max)
;; Reads from stdin.
"muttprint"
nil nil nil
(let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
(with-current-buffer buf
(let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ (notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
,@body)
(kill-buffer buf)))))
t)
(defun notmuch-show-insert-part-message/rfc822 (msg part _content-type _nth depth _button)
- (let* ((message (car (plist-get part :content)))
- (body (car (plist-get message :body)))
- (start (point)))
- ;; Override `notmuch-message-headers' to force `From' to be
- ;; displayed.
- (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
- (notmuch-show-insert-headers (plist-get message :headers)))
- ;; Blank line after headers to be compatible with the normal
- ;; message display.
- (insert "\n")
- ;; Show the body
- (notmuch-show-insert-bodypart msg body depth)
- (when notmuch-show-indent-multipart
- (indent-rigidly start (point) 1)))
- t)
+ (let ((message (car (plist-get part :content))))
+ (and
+ message
+ (let ((body (car (plist-get message :body)))
+ (start (point)))
+ ;; Override `notmuch-message-headers' to force `From' to be
+ ;; displayed.
+ (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
+ (notmuch-show-insert-headers (plist-get message :headers)))
+ ;; Blank line after headers to be compatible with the normal
+ ;; message display.
+ (insert "\n")
+ ;; Show the body
+ (notmuch-show-insert-bodypart msg body depth)
+ (when notmuch-show-indent-multipart
+ (indent-rigidly start (point) 1))
+ t))))
(defun notmuch-show-insert-part-text/plain (msg part _content-type _nth depth button)
;; For backward compatibility we want to apply the text/plain hook
(pop-to-buffer-same-window buf)
(erase-buffer)
(let ((coding-system-for-read 'no-conversion))
- (call-process notmuch-command nil t nil "show" "--format=raw" id))
+ (notmuch--call-process notmuch-command nil t nil "show" "--format=raw" id))
(goto-char (point-min))
(set-buffer-modified-p nil)
(setq buffer-read-only t)
(let ((cwd default-directory)
(buf (get-buffer-create (concat "*notmuch-pipe*"))))
(with-current-buffer buf
- (setq buffer-read-only nil)
- (erase-buffer)
- ;; Use the originating buffer's working directory instead of
- ;; that of the pipe buffer.
- (cd cwd)
- (let ((exit-code (call-process-shell-command shell-command nil buf)))
- (goto-char (point-max))
- (set-buffer-modified-p nil)
- (setq buffer-read-only t)
- (unless (zerop exit-code)
- (pop-to-buffer buf)
- (message (format "Command '%s' exited abnormally with code %d"
- shell-command exit-code))))))))
+ (setq buffer-read-only t)
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ ;; Use the originating buffer's working directory instead of
+ ;; that of the pipe buffer.
+ (cd cwd)
+ (let ((exit-code (call-process-shell-command shell-command nil buf)))
+ (goto-char (point-max))
+ (set-buffer-modified-p nil)
+ (unless (zerop exit-code)
+ (pop-to-buffer buf)
+ (message (format "Command '%s' exited abnormally with code %d"
+ shell-command exit-code)))))))))
(defun notmuch-show-tag-message (&rest tag-changes)
"Change tags for the current message.
(split-string
(with-output-to-string
(with-current-buffer standard-output
- (apply 'call-process notmuch-command nil t
+ (apply 'notmuch--call-process notmuch-command nil t
nil "search" "--output=tags" "--exclude=false" search-terms)))
"\n+" t))
name)
(mapconcat #'identity tag-change " "))))
(push (list key name-string
- `(lambda () (,tag-function ',tag-change)))
+ (lambda () (funcall tag-function tag-change)))
action-map)))
(push (list notmuch-tag-jump-reverse-key
(if reverse
notmuch-unthreaded-show-out
notmuch-tree-show-out))
+(defcustom notmuch-tree-thread-symbols
+ '((prefix . " ")
+ (top . "─")
+ (top-tee . "┬")
+ (vertical . "│")
+ (vertical-tee . "├")
+ (bottom . "╰")
+ (arrow . "►"))
+ "Strings used to draw trees in notmuch tree results.
+Symbol keys denote where the corresponding string value is used:
+`prefix' is used at the top of the tree, followed by `top' if it
+has no children or `top-tee' if it does; `vertical' is a bar
+connecting with a response down the list skipping the current
+one, while `vertical-tee' marks the current message as a reply to
+the previous one; `bottom' is used at the bottom of threads.
+Finally, the `arrrow' string in the list is used as a pointer to
+every message.
+
+Common customizations include setting `prefix' to \"-\", to see
+equal-length prefixes, and `arrow' to an empty string or to a
+different kind of arrow point."
+ :type '(alist :key-type symbol :value-type string)
+ :group 'notmuch-tree)
+
(defcustom notmuch-tree-result-format
`(("date" . "%12s ")
("authors" . "%-20s")
- ((("tree" . "%s")("subject" . "%s")) ." %-54s ")
+ ((("tree" . "%s")
+ ("subject" . "%s"))
+ . " %-54s ")
("tags" . "(%s)"))
- "Result formatting for tree view. Supported fields are: date,
-authors, subject, tree, tags. Tree means the thread tree
-box graphics. The field may also be a list in which case
-the formatting rules are applied recursively and then the
-output of all the fields in the list is inserted
-according to format-string.
-
-Note the author string should not contain
-whitespace (put it in the neighbouring fields instead).
-For example:
- (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
- \(\"subject\" . \"%s\"\)\)\)"
- :type '(alist :key-type (string) :value-type (string))
+ "Result formatting for tree view.
+
+Supported fields are: date, authors, subject, tree, tags.
+
+Tree means the thread tree box graphics. The field may
+also be a list in which case the formatting rules are
+applied recursively and then the output of all the fields
+in the list is inserted according to format-string.
+
+Note that the author string should not contain whitespace
+\(put it in the neighbouring fields instead). For example:
+ (setq notmuch-tree-result-format
+ '((\"authors\" . \"%-40s\")
+ (\"subject\" . \"%s\")))"
+ :type '(alist :key-type (choice string
+ (alist :key-type string
+ :value-type string))
+ :value-type string)
:group 'notmuch-tree)
(defcustom notmuch-unthreaded-result-format
("authors" . "%-20s")
((("subject" . "%s")) ." %-54s ")
("tags" . "(%s)"))
- "Result formatting for unthreaded tree view. Supported fields are: date,
-authors, subject, tree, tags. Tree means the thread tree
-box graphics. The field may also be a list in which case
-the formatting rules are applied recursively and then the
-output of all the fields in the list is inserted
-according to format-string.
-
-Note the author string should not contain
-whitespace (put it in the neighbouring fields instead).
-For example:
- (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
- \(\"subject\" . \"%s\"\)\)\)"
- :type '(alist :key-type (string) :value-type (string))
+ "Result formatting for unthreaded tree view.
+
+Supported fields are: date, authors, subject, tree, tags.
+
+Tree means the thread tree box graphics. The field may
+also be a list in which case the formatting rules are
+applied recursively and then the output of all the fields
+in the list is inserted according to format-string.
+
+Note that the author string should not contain whitespace
+\(put it in the neighbouring fields instead). For example:
+ (setq notmuch-unthreaded-result-format
+ '((\"authors\" . \"%-40s\")
+ (\"subject\" . \"%s\")))"
+ :type '(alist :key-type (choice string
+ (alist :key-type string
+ :value-type string))
+ :value-type string)
:group 'notmuch-tree)
(defun notmuch-tree-result-format ()
((listp field)
(format format-string (notmuch-tree-format-field-list field msg)))
+ ((functionp field)
+ (funcall field format-string msg))
+
((string-equal field "date")
(let ((face (if match
'notmuch-tree-match-date-face
(replies (cadr tree)))
(cond
((and (< 0 depth) (not last))
- (push "├" tree-status))
+ (push (alist-get 'vertical-tee notmuch-tree-thread-symbols) tree-status))
((and (< 0 depth) last)
- (push "╰" tree-status))
+ (push (alist-get 'bottom notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) first last)
- ;; Choice between these two variants is a matter of taste.
- ;; (push "─" tree-status))
- (push " " tree-status))
+ (push (alist-get 'prefix notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) first (not last))
- (push "┬" tree-status))
+ (push (alist-get 'top-tee notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) (not first) last)
- (push "╰" tree-status))
+ (push (alist-get 'bottom notmuch-tree-thread-symbols) tree-status))
((and (eq 0 depth) (not first) (not last))
- (push "├" tree-status)))
- (push (concat (if replies "┬" "─") "►") tree-status)
+ (push (alist-get 'vertical-tee notmuch-tree-thread-symbols) tree-status)))
+ (push (concat (alist-get (if replies 'top-tee 'top) notmuch-tree-thread-symbols)
+ (alist-get 'arrow notmuch-tree-thread-symbols))
+ tree-status)
(setq msg (plist-put msg :first (and first (eq 0 depth))))
(setq msg (plist-put msg :tree-status tree-status))
(setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
(pop tree-status)
(if last
(push " " tree-status)
- (push "│" tree-status))
+ (push (alist-get 'vertical notmuch-tree-thread-symbols) tree-status))
(notmuch-tree-insert-thread replies (1+ depth) tree-status)))
(defun notmuch-tree-insert-thread (thread depth tree-status)
(concat " and (" query-context ")"))))
(sort-arg (if oldest-first "--sort=oldest-first" "--sort=newest-first"))
(message-arg (if unthreaded "--unthreaded" "--entire-thread")))
- (when (equal (car (process-lines notmuch-command "count" search-args)) "0")
+ (when (equal (car (notmuch--process-lines notmuch-command "count" search-args)) "0")
(setq search-args basic-query))
(notmuch-tag-clear-cache)
(let ((proc (notmuch-start-notmuch
("authors" . "%-20s ")
("subject" . "%s ")
("tags" . "(%s)"))
- "Search result formatting. Supported fields are:
- date, count, authors, subject, tags
+ "Search result formatting.
+
+Supported fields are: date, count, authors, subject, tags.
For example:
- (setq notmuch-search-result-format \(\(\"authors\" . \"%-40s\"\)
- \(\"subject\" . \"%s\"\)\)\)
+ (setq notmuch-search-result-format
+ '((\"authors\" . \"%-40s\")
+ (\"subject\" . \"%s\")))
+
Line breaks are permitted in format strings (though this is
currently experimental). Note that a line break at the end of an
\"authors\" field will get elided if the authors list is long;
place it instead at the beginning of the following field. To
enter a line break when setting this variable with setq, use \\n.
To enter a line break in customize, press \\[quoted-insert] C-j."
- :type '(alist :key-type (string) :value-type (string))
+ :type '(alist :key-type string :value-type string)
:group 'notmuch-search)
;; The name of this variable `notmuch-init-file' is consistent with the
(insert padding))))
(defun notmuch-search-insert-field (field format-string result)
- (cond
- ((string-equal field "date")
- (insert (propertize (format format-string (plist-get result :date_relative))
- 'face 'notmuch-search-date)))
- ((string-equal field "count")
- (insert (propertize (format format-string
- (format "[%s/%s]" (plist-get result :matched)
- (plist-get result :total)))
- 'face 'notmuch-search-count)))
- ((string-equal field "subject")
- (insert (propertize (format format-string
- (notmuch-sanitize (plist-get result :subject)))
- 'face 'notmuch-search-subject)))
- ((string-equal field "authors")
- (notmuch-search-insert-authors
- format-string (notmuch-sanitize (plist-get result :authors))))
- ((string-equal field "tags")
- (let ((tags (plist-get result :tags))
- (orig-tags (plist-get result :orig-tags)))
- (insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
+ (pcase field
+ ((pred functionp)
+ (insert (funcall field format-string result)))
+ ("date"
+ (insert (propertize (format format-string (plist-get result :date_relative))
+ 'face 'notmuch-search-date)))
+ ("count"
+ (insert (propertize (format format-string
+ (format "[%s/%s]" (plist-get result :matched)
+ (plist-get result :total)))
+ 'face 'notmuch-search-count)))
+ ("subject"
+ (insert (propertize (format format-string
+ (notmuch-sanitize (plist-get result :subject)))
+ 'face 'notmuch-search-subject)))
+ ("authors"
+ (notmuch-search-insert-authors format-string
+ (notmuch-sanitize (plist-get result :authors))))
+ ("tags"
+ (let ((tags (plist-get result :tags))
+ (orig-tags (plist-get result :orig-tags)))
+ (insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
(defun notmuch-search-show-result (result pos)
"Insert RESULT at POS."
PROMPT is the string to prompt with."
(let* ((all-tags
(mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
- (process-lines notmuch-command "search" "--output=tags" "*")))
+ (notmuch--process-lines notmuch-command "search" "--output=tags" "*")))
(completions
(append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
"subject:" "attachment:")
$(dir)/features.cc \
$(dir)/prefix.cc \
$(dir)/open.cc \
- $(dir)/init.cc
+ $(dir)/init.cc \
+ $(dir)/parse-sexp.cc
libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
return HAVE_XAPIAN_DB_RETRY_LOCK;
} else if (STRNCMP_LITERAL (name, "session_key") == 0) {
return true;
+ } else if (STRNCMP_LITERAL (name, "sexpr_query") == 0) {
+ return HAVE_SFSEXP;
} else {
return false;
}
for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
const char *key = notmuch_config_list_key (list);
- char *normalized_val = _expand_path (list, key, notmuch_config_list_value (list));
+ char *normalized_val = NULL;
+
+ /* If we opened from a given path, do not overwrite it */
+ if (strcmp (key, "database.path") == 0 &&
+ (notmuch->params & NOTMUCH_PARAM_DATABASE) &&
+ notmuch->xapian_db)
+ continue;
+
+ normalized_val = _expand_path (list, key, notmuch_config_list_value (list));
_notmuch_string_map_append (notmuch->config, key, normalized_val);
talloc_free (normalized_val);
}
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
+
+ /* If we opened from a given path, do not overwrite it */
+ if (strcmp (absolute_key, "database.path") == 0 &&
+ (notmuch->params & NOTMUCH_PARAM_DATABASE) &&
+ notmuch->xapian_db)
+ continue;
+
normalized_val = _expand_path (notmuch, absolute_key, val);
_notmuch_string_map_set (notmuch->config, absolute_key, normalized_val);
g_free (val);
#include <xapian.h>
+#if HAVE_SFSEXP
+#include <sexp.h>
+#endif
+
/* Bit masks for _notmuch_database::features. Features are named,
* independent aspects of the database schema.
*
Xapian::QueryParser::FLAG_WILDCARD | \
Xapian::QueryParser::FLAG_PURE_NOT)
+/*
+ * Which parameters were explicit when the database was opened */
+typedef enum {
+ NOTMUCH_PARAM_NONE = 0,
+ NOTMUCH_PARAM_DATABASE = 1 << 0,
+ NOTMUCH_PARAM_CONFIG = 1 << 1,
+ NOTMUCH_PARAM_PROFILE = 1 << 2,
+} notmuch_open_param_t;
+
+/*
+ * define bitwise operators to hide casts */
+
+inline notmuch_open_param_t
+operator| (notmuch_open_param_t a, notmuch_open_param_t b)
+{
+ return static_cast<notmuch_open_param_t>(
+ static_cast<unsigned>(a) | static_cast<unsigned>(b));
+}
+
+inline notmuch_open_param_t&
+operator|= (notmuch_open_param_t &a, notmuch_open_param_t b)
+{
+ a = a | b;
+ return a;
+}
+
+inline notmuch_open_param_t
+operator& (notmuch_open_param_t a, notmuch_open_param_t b)
+{
+ return static_cast<notmuch_open_param_t>(
+ static_cast<unsigned>(a) & static_cast<unsigned>(b));
+}
+
struct _notmuch_database {
bool exception_reported;
*/
unsigned long view;
Xapian::QueryParser *query_parser;
+ Xapian::Stem *stemmer;
Xapian::TermGenerator *term_gen;
Xapian::RangeProcessor *value_range_processor;
Xapian::RangeProcessor *date_range_processor;
/* Cached and possibly overridden configuration */
notmuch_string_map_t *config;
+
+ /* Track what parameters were specified when opening */
+ notmuch_open_param_t params;
};
/* Prior to database version 3, features were implied by the database
notmuch_status_t
_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch);
+#if __cplusplus
+/* query.cc */
+notmuch_status_t
+_notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
+ std::string query_string,
+ Xapian::Query &output,
+ std::string &msg);
+/* parse-sexp.cc */
+notmuch_status_t
+_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
+ Xapian::Query &output);
+
+notmuch_status_t
+_notmuch_query_expand (notmuch_database_t *notmuch, const char *field, Xapian::Query subquery,
+ Xapian::Query &output, std::string &msg);
+
+/* regexp-fields.cc */
+notmuch_status_t
+_notmuch_regexp_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
+ std::string regexp_str,
+ Xapian::Query &output, std::string &msg);
+
+/* thread-fp.cc */
+notmuch_status_t
+_notmuch_query_name_to_query (notmuch_database_t *notmuch, const std::string name,
+ Xapian::Query &output);
+
+#if HAVE_SFSEXP
+/* parse-sexp.cc */
+notmuch_status_t
+_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
+ Xapian::Query &output);
+#endif
+#endif
#endif
return "No database found";
case NOTMUCH_STATUS_DATABASE_EXISTS:
return "Database exists, not recreated";
+ case NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
+ return "Syntax error in query";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
if (value)
return value;
- if (strcasecmp (header, "received") == 0) {
+ if (strcasecmp (header, "received") == 0 ||
+ strcasecmp (header, "delivered-to") == 0) {
/*
- * The Received: header is special. We concatenate all
- * instances of the header as we use this when analyzing the
- * path the mail has taken from sender to recipient.
+ * The Received: header is special. We concatenate all instances of the
+ * header as we use this when analyzing the path the mail has taken
+ * from sender to recipient.
+ *
+ * Similarly, multiple instances of Delivered-To may be present. We
+ * concatenate them so the one with highest priority may be picked (eg.
+ * primary_email before other_email).
*/
decoded = _notmuch_message_file_get_combined_header (message, header);
} else {
* version in Makefile.local.
*/
#define LIBNOTMUCH_MAJOR_VERSION 5
-#define LIBNOTMUCH_MINOR_VERSION 4
+#define LIBNOTMUCH_MINOR_VERSION 5
#define LIBNOTMUCH_MICRO_VERSION 0
* Database exists, so not (re)-created
*/
NOTMUCH_STATUS_DATABASE_EXISTS,
+ /**
+ * Syntax error in query
+ */
+ NOTMUCH_STATUS_BAD_QUERY_SYNTAX,
/**
* Not an actual status value. Just a way to find out how many
* valid status values there are.
* @retval NOTMUCH_STATUS_NULL_POINTER: The given \a database
* argument is NULL.
*
+ * @retval NOTMUCH_STATUS_NO_CONFIG: No config file was found. Fatal.
+ *
* @retval NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
*
* @retval NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
*
* For description of arguments, @see notmuch_database_open_with_config
*
+ * For errors other then NO_DATABASE and NO_CONFIG, *database is set to
+ * NULL.
+ *
* @retval NOTMUCH_STATUS_SUCCESS: Successfully loaded configuration.
*
* @retval NOTMUCH_STATUS_NO_CONFIG: No config file was loaded. Not fatal.
*
* For description of arguments, @see notmuch_database_open_with_config
*
+ * In case of any failure, this function returns an error status and
+ * sets *database to NULL.
+ *
* @retval NOTMUCH_STATUS_SUCCESS: Successfully created the database.
*
* @retval NOTMUCH_STATUS_DATABASE_EXISTS: Database already exists, not created
notmuch_query_create (notmuch_database_t *database,
const char *query_string);
+typedef enum {
+ NOTMUCH_QUERY_SYNTAX_XAPIAN,
+ NOTMUCH_QUERY_SYNTAX_SEXP
+} notmuch_query_syntax_t;
+
+notmuch_status_t
+notmuch_query_create_with_syntax (notmuch_database_t *database,
+ const char *query_string,
+ notmuch_query_syntax_t syntax,
+ notmuch_query_t **output);
/**
* Sort values for notmuch_query_set_sort.
*/
}
static notmuch_database_t *
-_alloc_notmuch ()
+_alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
{
notmuch_database_t *notmuch;
notmuch->transaction_count = 0;
notmuch->transaction_threshold = 0;
notmuch->view = 1;
+
+ notmuch->params = NOTMUCH_PARAM_NONE;
+ if (database_path)
+ notmuch->params |= NOTMUCH_PARAM_DATABASE;
+ if (config_path)
+ notmuch->params |= NOTMUCH_PARAM_CONFIG;
+ if (profile)
+ notmuch->params |= NOTMUCH_PARAM_PROFILE;
+
return notmuch;
}
" has a newer database format version (%u) than supported by this\n"
" version of notmuch (%u).\n",
database_path, version, NOTMUCH_DATABASE_VERSION));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
" requires features (%s)\n"
" not supported by this version of notmuch.\n",
database_path, incompat_features));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
"lastmod:");
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
notmuch->query_parser->set_database (*notmuch->xapian_db);
- notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
+ notmuch->stemmer = new Xapian::Stem ("english");
+ notmuch->query_parser->set_stemmer (*notmuch->stemmer);
notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
} catch (const Xapian::Error &error) {
IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
error.get_msg ().c_str ()));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
DONE:
_notmuch_init ();
- notmuch = _alloc_notmuch ();
+ notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
free (message);
}
+ if (status && notmuch) {
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ }
+
if (database)
*database = notmuch;
- else
- talloc_free (notmuch);
if (notmuch)
notmuch->open = true;
_notmuch_init ();
- notmuch = _alloc_notmuch ();
+ notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
else
free (message);
}
+ if (status && notmuch) {
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ }
+
if (database)
*database = notmuch;
- else
- talloc_free (notmuch);
+
+ if (notmuch)
+ notmuch->open = true;
return status;
}
_notmuch_init ();
- notmuch = _alloc_notmuch ();
+ notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
if (status_string)
*status_string = message;
+ if (status &&
+ status != NOTMUCH_STATUS_NO_DATABASE
+ && status != NOTMUCH_STATUS_NO_CONFIG) {
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ }
+
if (database)
*database = notmuch;
--- /dev/null
+#include "database-private.h"
+
+#if HAVE_SFSEXP
+#include "sexp.h"
+#include "unicode-util.h"
+
+/* _sexp is used for file scope symbols to avoid clashing with
+ * definitions from sexp.h */
+
+/* sexp_binding structs attach name to a sexp and a defining
+ * context. The latter allows lazy evaluation of parameters whose
+ * definition contains other parameters. Lazy evaluation is needed
+ * because a primary goal of macros is to change the parent field for
+ * a sexp.
+ */
+
+typedef struct sexp_binding {
+ const char *name;
+ const sexp_t *sx;
+ const struct sexp_binding *context;
+ const struct sexp_binding *next;
+} _sexp_binding_t;
+
+typedef enum {
+ SEXP_FLAG_NONE = 0,
+ SEXP_FLAG_FIELD = 1 << 0,
+ SEXP_FLAG_BOOLEAN = 1 << 1,
+ SEXP_FLAG_SINGLE = 1 << 2,
+ SEXP_FLAG_WILDCARD = 1 << 3,
+ SEXP_FLAG_REGEX = 1 << 4,
+ SEXP_FLAG_DO_REGEX = 1 << 5,
+ SEXP_FLAG_EXPAND = 1 << 6,
+ SEXP_FLAG_DO_EXPAND = 1 << 7,
+ SEXP_FLAG_ORPHAN = 1 << 8,
+} _sexp_flag_t;
+
+/*
+ * define bitwise operators to hide casts */
+
+inline _sexp_flag_t
+operator| (_sexp_flag_t a, _sexp_flag_t b)
+{
+ return static_cast<_sexp_flag_t>(
+ static_cast<unsigned>(a) | static_cast<unsigned>(b));
+}
+
+inline _sexp_flag_t
+operator& (_sexp_flag_t a, _sexp_flag_t b)
+{
+ return static_cast<_sexp_flag_t>(
+ static_cast<unsigned>(a) & static_cast<unsigned>(b));
+}
+
+typedef struct {
+ const char *name;
+ Xapian::Query::op xapian_op;
+ Xapian::Query initial;
+ _sexp_flag_t flags;
+} _sexp_prefix_t;
+
+static _sexp_prefix_t prefixes[] =
+{
+ { "and", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_NONE },
+ { "attachment", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
+ { "body", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD },
+ { "from", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "folder", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
+ { "infix", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
+ SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
+ { "is", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_DO_EXPAND },
+ { "mid", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
+ { "mimetype", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
+ { "not", Xapian::Query::OP_AND_NOT, Xapian::Query::MatchAll,
+ SEXP_FLAG_NONE },
+ { "of", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_DO_EXPAND },
+ { "or", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
+ SEXP_FLAG_NONE },
+ { "path", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
+ { "property", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "query", Xapian::Query::OP_INVALID, Xapian::Query::MatchNothing,
+ SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
+ { "regex", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
+ SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
+ { "rx", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
+ SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
+ { "starts-with", Xapian::Query::OP_WILDCARD, Xapian::Query::MatchAll,
+ SEXP_FLAG_SINGLE },
+ { "subject", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "tag", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "thread", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
+ SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
+ { "to", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
+ { }
+};
+
+static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env,
+ const sexp_t *sx,
+ Xapian::Query &output);
+
+static notmuch_status_t
+_sexp_combine_query (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env,
+ Xapian::Query::op operation,
+ Xapian::Query left,
+ const sexp_t *sx,
+ Xapian::Query &output)
+{
+ Xapian::Query subquery;
+
+ notmuch_status_t status;
+
+ /* if we run out elements, return accumulator */
+
+ if (! sx) {
+ output = left;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ status = _sexp_to_xapian_query (notmuch, parent, env, sx, subquery);
+ if (status)
+ return status;
+
+ return _sexp_combine_query (notmuch,
+ parent,
+ env,
+ operation,
+ Xapian::Query (operation, left, subquery),
+ sx->next, output);
+}
+
+static notmuch_status_t
+_sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
+{
+ Xapian::Utf8Iterator p (phrase);
+ Xapian::Utf8Iterator end;
+ std::vector<std::string> terms;
+
+ while (p != end) {
+ Xapian::Utf8Iterator start;
+ while (p != end && ! Xapian::Unicode::is_wordchar (*p))
+ p++;
+
+ if (p == end)
+ break;
+
+ start = p;
+
+ while (p != end && Xapian::Unicode::is_wordchar (*p))
+ p++;
+
+ if (p != start) {
+ std::string word (start, p);
+ word = Xapian::Unicode::tolower (word);
+ terms.push_back (term_prefix + word);
+ }
+ }
+ output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_sexp_parse_wildcard (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *parent,
+ unused(const _sexp_binding_t *env),
+ std::string match,
+ Xapian::Query &output)
+{
+
+ std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
+
+ if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) {
+ _notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ output = Xapian::Query (Xapian::Query::OP_WILDCARD,
+ term_prefix + Xapian::Unicode::tolower (match));
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_sexp_parse_one_term (notmuch_database_t *notmuch, std::string term_prefix, const sexp_t *sx,
+ Xapian::Query &output)
+{
+ Xapian::Stem stem = *(notmuch->stemmer);
+
+ if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
+ std::string term = Xapian::Unicode::tolower (sx->val);
+
+ output = Xapian::Query ("Z" + term_prefix + stem (term));
+ return NOTMUCH_STATUS_SUCCESS;
+ } else {
+ return _sexp_parse_phrase (term_prefix, sx->val, output);
+ }
+
+}
+
+notmuch_status_t
+_sexp_parse_regex (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
+ unused(const _sexp_binding_t *env),
+ std::string val, Xapian::Query &output)
+{
+ if (! parent) {
+ _notmuch_database_log (notmuch, "illegal '%s' outside field\n",
+ prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ if (! (parent->flags & SEXP_FLAG_REGEX)) {
+ _notmuch_database_log (notmuch, "'%s' not supported in field '%s'\n",
+ prefix->name, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ std::string msg; /* ignored */
+
+ return _notmuch_regexp_to_query (notmuch, Xapian::BAD_VALUENO, parent->name,
+ val, output, msg);
+}
+
+
+static notmuch_status_t
+_sexp_expand_query (notmuch_database_t *notmuch,
+ const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
+ unused(const _sexp_binding_t *env), const sexp_t *sx, Xapian::Query &output)
+{
+ Xapian::Query subquery;
+ notmuch_status_t status;
+ std::string msg;
+
+ if (! (parent->flags & SEXP_FLAG_EXPAND)) {
+ _notmuch_database_log (notmuch, "'%s' unsupported inside '%s'\n", prefix->name, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
+ subquery);
+ if (status)
+ return status;
+
+ status = _notmuch_query_expand (notmuch, parent->name, subquery, output, msg);
+ if (status) {
+ _notmuch_database_log (notmuch, "error expanding query %s\n", msg.c_str ());
+ }
+ return status;
+}
+
+static notmuch_status_t
+_sexp_parse_infix (notmuch_database_t *notmuch, const sexp_t *sx, Xapian::Query &output)
+{
+ try {
+ output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
+ } catch (const Xapian::QueryParserError &error) {
+ _notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ } catch (const Xapian::Error &error) {
+ if (! notmuch->exception_reported) {
+ _notmuch_database_log (notmuch,
+ "A Xapian exception occurred parsing query: %s\n",
+ error.get_msg ().c_str ());
+ _notmuch_database_log_append (notmuch,
+ "Query string was: %s\n",
+ sx->val);
+ notmuch->exception_reported = true;
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
+{
+ _sexp_prefix_t user_prefix;
+
+ user_prefix.name = sx->list->val;
+ user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
+
+ if (parent) {
+ _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
+ sx->list->val, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ parent = &user_prefix;
+
+ return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
+ sx->list->next, output);
+}
+
+static _sexp_binding_t *
+_sexp_bind (void *ctx, const _sexp_binding_t *env, const char *name, const sexp_t *sx, const
+ _sexp_binding_t *context)
+{
+ _sexp_binding_t *binding = talloc (ctx, _sexp_binding_t);
+
+ binding->name = talloc_strdup (ctx, name);
+ binding->sx = sx;
+ binding->context = context;
+ binding->next = env;
+ return binding;
+}
+
+static notmuch_status_t
+maybe_apply_macro (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, const sexp_t *args,
+ Xapian::Query &output)
+{
+ const sexp_t *params, *param, *arg, *body;
+ void *local = talloc_new (notmuch);
+ _sexp_binding_t *new_env = NULL;
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+ if (sx->list->ty != SEXP_VALUE || strcmp (sx->list->val, "macro") != 0) {
+ status = NOTMUCH_STATUS_IGNORED;
+ goto DONE;
+ }
+
+ params = sx->list->next;
+
+ if (! params || (params->ty != SEXP_LIST)) {
+ _notmuch_database_log (notmuch, "missing (possibly empty) list of arguments to macro\n");
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ body = params->next;
+
+ if (! body) {
+ _notmuch_database_log (notmuch, "missing body of macro\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ for (param = params->list, arg = args;
+ param && arg;
+ param = param->next, arg = arg->next) {
+ if (param->ty != SEXP_VALUE || param->aty != SEXP_BASIC) {
+ _notmuch_database_log (notmuch, "macro parameters must be unquoted atoms\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+ new_env = _sexp_bind (local, new_env, param->val, arg, env);
+ }
+
+ if (param && ! arg) {
+ _notmuch_database_log (notmuch, "too few arguments to macro\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ if (! param && arg) {
+ _notmuch_database_log (notmuch, "too many arguments to macro\n");
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ status = _sexp_to_xapian_query (notmuch, parent, new_env, body, output);
+
+ DONE:
+ if (local)
+ talloc_free (local);
+
+ return status;
+}
+
+static notmuch_status_t
+maybe_saved_squery (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
+{
+ char *key;
+ char *expansion = NULL;
+ notmuch_status_t status;
+ sexp_t *saved_sexp;
+ void *local = talloc_new (notmuch);
+ char *buf;
+
+ key = talloc_asprintf (local, "squery.%s", sx->list->val);
+ if (! key) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ status = notmuch_database_get_config (notmuch, key, &expansion);
+ if (status)
+ goto DONE;
+ if (EMPTY_STRING (expansion)) {
+ status = NOTMUCH_STATUS_IGNORED;
+ goto DONE;
+ }
+
+ buf = talloc_strdup (local, expansion);
+ /* XXX TODO: free this memory */
+ saved_sexp = parse_sexp (buf, strlen (expansion));
+ if (! saved_sexp) {
+ _notmuch_database_log (notmuch, "invalid saved s-expression query: '%s'\n", expansion);
+ status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ goto DONE;
+ }
+
+ status = maybe_apply_macro (notmuch, parent, env, saved_sexp, sx->list->next, output);
+ if (status == NOTMUCH_STATUS_IGNORED)
+ status = _sexp_to_xapian_query (notmuch, parent, env, saved_sexp, output);
+
+ DONE:
+ if (local)
+ talloc_free (local);
+
+ return status;
+}
+
+static notmuch_status_t
+_sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const char *name,
+ Xapian::Query &output)
+{
+ for (; env; env = env->next) {
+ if (strcmp (name, env->name) == 0) {
+ return _sexp_to_xapian_query (notmuch, parent, env->context, env->sx,
+ output);
+ }
+ }
+ _notmuch_database_log (notmuch, "undefined parameter %s\n", name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+}
+
+/* Here we expect the s-expression to be a proper list, with first
+ * element defining and operation, or as a special case the empty
+ * list */
+
+static notmuch_status_t
+_sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
+ const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
+{
+ notmuch_status_t status;
+
+ if (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
+ return _sexp_expand_param (notmuch, parent, env, sx->val + 1, output);
+ }
+
+ if (sx->ty == SEXP_VALUE) {
+ std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
+
+ if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
+ return _sexp_parse_wildcard (notmuch, parent, env, "", output);
+ }
+
+ if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
+ output = Xapian::Query (term_prefix + sx->val);
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ if (parent) {
+ return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
+ } else {
+ Xapian::Query accumulator;
+ for (_sexp_prefix_t *prefix = prefixes; prefix->name; prefix++) {
+ if (prefix->flags & SEXP_FLAG_FIELD) {
+ Xapian::Query subquery;
+ term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
+ status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
+ if (status)
+ return status;
+ accumulator = Xapian::Query (Xapian::Query::OP_OR, accumulator, subquery);
+ }
+ }
+ output = accumulator;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+ }
+
+ /* Empty list */
+ if (! sx->list) {
+ output = Xapian::Query::MatchAll;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ if (sx->list->ty == SEXP_LIST) {
+ _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
+ sx->list->val);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ status = maybe_saved_squery (notmuch, parent, env, sx, output);
+ if (status != NOTMUCH_STATUS_IGNORED)
+ return status;
+
+ /* Check for user defined field */
+ if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
+ return _sexp_parse_header (notmuch, parent, env, sx, output);
+ }
+
+ if (strcmp (sx->list->val, "macro") == 0) {
+ _notmuch_database_log (notmuch, "macro definition not permitted here\n");
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
+ if (strcmp (prefix->name, sx->list->val) == 0) {
+ if (prefix->flags & SEXP_FLAG_FIELD) {
+ if (parent) {
+ _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
+ prefix->name, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+ parent = prefix;
+ }
+
+ if (parent && (prefix->flags & SEXP_FLAG_ORPHAN)) {
+ _notmuch_database_log (notmuch, "'%s' not supported inside '%s'\n",
+ prefix->name, parent->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ if ((prefix->flags & SEXP_FLAG_SINGLE) &&
+ (! sx->list->next || sx->list->next->next || sx->list->next->ty != SEXP_VALUE)) {
+ _notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
+ prefix->name);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ if (strcmp (prefix->name, "infix") == 0) {
+ return _sexp_parse_infix (notmuch, sx->list->next, output);
+ }
+
+ if (strcmp (prefix->name, "query") == 0) {
+ return _notmuch_query_name_to_query (notmuch, sx->list->next->val, output);
+ }
+
+ if (prefix->xapian_op == Xapian::Query::OP_WILDCARD)
+ return _sexp_parse_wildcard (notmuch, parent, env, sx->list->next->val, output);
+
+ if (prefix->flags & SEXP_FLAG_DO_REGEX) {
+ return _sexp_parse_regex (notmuch, prefix, parent, env, sx->list->next->val, output);
+ }
+
+ if (prefix->flags & SEXP_FLAG_DO_EXPAND) {
+ return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
+ }
+
+ return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
+ sx->list->next, output);
+ }
+ }
+
+ _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+}
+
+notmuch_status_t
+_notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
+ Xapian::Query &output)
+{
+ const sexp_t *sx = NULL;
+ char *buf = talloc_strdup (notmuch, querystr);
+
+ sx = parse_sexp (buf, strlen (querystr));
+ if (! sx) {
+ _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
+ return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+ }
+
+ return _sexp_to_xapian_query (notmuch, NULL, NULL, sx, output);
+}
+#endif
#include "query-fp.h"
#include <iostream>
-Xapian::Query
-QueryFieldProcessor::operator() (const std::string & name)
+notmuch_status_t
+_notmuch_query_name_to_query (notmuch_database_t *notmuch, const std::string name,
+ Xapian::Query &output)
{
std::string key = "query." + name;
char *expansion;
notmuch_status_t status;
status = notmuch_database_get_config (notmuch, key.c_str (), &expansion);
+ if (status)
+ return status;
+
+ output = notmuch->query_parser->parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+Xapian::Query
+QueryFieldProcessor::operator() (const std::string & name)
+{
+ notmuch_status_t status;
+ Xapian::Query output;
+
+ status = _notmuch_query_name_to_query (notmuch, name, output);
if (status) {
throw Xapian::QueryParserError ("error looking up key" + name);
}
- return parser.parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
+ return output;
+
}
notmuch_string_list_t *exclude_terms;
notmuch_exclude_t omit_excluded;
bool parsed;
+ notmuch_query_syntax_t syntax;
Xapian::Query xapian_query;
std::set<std::string> terms;
};
return 0;
}
-notmuch_query_t *
-notmuch_query_create (notmuch_database_t *notmuch,
- const char *query_string)
+static notmuch_query_t *
+_notmuch_query_constructor (notmuch_database_t *notmuch,
+ const char *query_string)
{
notmuch_query_t *query;
query->notmuch = notmuch;
- query->query_string = talloc_strdup (query, query_string);
+ if (query_string)
+ query->query_string = talloc_strdup (query, query_string);
+ else
+ query->query_string = NULL;
query->sort = NOTMUCH_SORT_NEWEST_FIRST;
return query;
}
-static notmuch_status_t
-_notmuch_query_ensure_parsed (notmuch_query_t *query)
+notmuch_query_t *
+notmuch_query_create (notmuch_database_t *notmuch,
+ const char *query_string)
{
- if (query->parsed)
- return NOTMUCH_STATUS_SUCCESS;
- try {
- query->xapian_query =
- query->notmuch->query_parser->
- parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS);
+ notmuch_query_t *query;
+ notmuch_status_t status;
- /* Xapian doesn't support skip_to on terms from a query since
- * they are unordered, so cache a copy of all terms in
- * something searchable.
- */
+ status = notmuch_query_create_with_syntax (notmuch, query_string,
+ NOTMUCH_QUERY_SYNTAX_XAPIAN,
+ &query);
+ if (status)
+ return NULL;
+
+ return query;
+}
+
+notmuch_status_t
+notmuch_query_create_with_syntax (notmuch_database_t *notmuch,
+ const char *query_string,
+ notmuch_query_syntax_t syntax,
+ notmuch_query_t **output)
+{
+
+ notmuch_query_t *query;
+
+ if (! output)
+ return NOTMUCH_STATUS_NULL_POINTER;
- for (Xapian::TermIterator t = query->xapian_query.get_terms_begin ();
- t != query->xapian_query.get_terms_end (); ++t)
- query->terms.insert (*t);
+ query = _notmuch_query_constructor (notmuch, query_string);
+ if (! query)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ if (syntax == NOTMUCH_QUERY_SYNTAX_SEXP && ! HAVE_SFSEXP) {
+ _notmuch_database_log (notmuch, "sexp query parser not available");
+ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+ }
- query->parsed = true;
+ query->syntax = syntax;
+ *output = query;
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static void
+_notmuch_query_cache_terms (notmuch_query_t *query)
+{
+ /* Xapian doesn't support skip_to on terms from a query since
+ * they are unordered, so cache a copy of all terms in
+ * something searchable.
+ */
+
+ for (Xapian::TermIterator t = query->xapian_query.get_terms_begin ();
+ t != query->xapian_query.get_terms_end (); ++t)
+ query->terms.insert (*t);
+}
+
+notmuch_status_t
+_notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
+ std::string query_string,
+ Xapian::Query &output,
+ std::string &msg)
+{
+ try {
+ if (query_string == "" || query_string == "*") {
+ output = Xapian::Query::MatchAll;
+ } else {
+ output =
+ notmuch->query_parser->
+ parse_query (query_string, NOTMUCH_QUERY_PARSER_FLAGS);
+ }
} catch (const Xapian::Error &error) {
- if (! query->notmuch->exception_reported) {
- _notmuch_database_log (query->notmuch,
+ if (! notmuch->exception_reported) {
+ _notmuch_database_log (notmuch,
"A Xapian exception occurred parsing query: %s\n",
error.get_msg ().c_str ());
- _notmuch_database_log_append (query->notmuch,
+ _notmuch_database_log_append (notmuch,
"Query string was: %s\n",
- query->query_string);
- query->notmuch->exception_reported = true;
+ query_string.c_str ());
+ notmuch->exception_reported = true;
}
+ msg = error.get_msg ();
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return NOTMUCH_STATUS_SUCCESS;
}
+static notmuch_status_t
+_notmuch_query_ensure_parsed_xapian (notmuch_query_t *query)
+{
+ notmuch_status_t status;
+ std::string msg; /* ignored */
+
+ status = _notmuch_query_string_to_xapian_query (query->notmuch, query->query_string,
+ query->xapian_query, msg);
+ if (status)
+ return status;
+
+ query->parsed = true;
+
+ _notmuch_query_cache_terms (query);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_notmuch_query_ensure_parsed_sexpr (notmuch_query_t *query)
+{
+ notmuch_status_t status;
+
+ if (query->parsed)
+ return NOTMUCH_STATUS_SUCCESS;
+
+ status = _notmuch_sexp_string_to_xapian_query (query->notmuch, query->query_string,
+ query->xapian_query);
+ if (status)
+ return status;
+
+ _notmuch_query_cache_terms (query);
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_notmuch_query_ensure_parsed (notmuch_query_t *query)
+{
+ if (query->parsed)
+ return NOTMUCH_STATUS_SUCCESS;
+
+#if HAVE_SFSEXP
+ if (query->syntax == NOTMUCH_QUERY_SYNTAX_SEXP)
+ return _notmuch_query_ensure_parsed_sexpr (query);
+#endif
+
+ return _notmuch_query_ensure_parsed_xapian (query);
+}
+
const char *
notmuch_query_get_query_string (const notmuch_query_t *query)
{
notmuch_messages_t **out)
{
notmuch_database_t *notmuch = query->notmuch;
- const char *query_string = query->query_string;
notmuch_mset_messages_t *messages;
notmuch_status_t status;
Xapian::MSet mset;
Xapian::MSetIterator iterator;
- if (strcmp (query_string, "") == 0 ||
- strcmp (query_string, "*") == 0) {
- final_query = mail_query;
- } else {
- final_query = Xapian::Query (Xapian::Query::OP_AND,
- mail_query, query->xapian_query);
- }
+ final_query = Xapian::Query (Xapian::Query::OP_AND,
+ mail_query, query->xapian_query);
+
messages->base.excluded_doc_ids = NULL;
if ((query->omit_excluded != NOTMUCH_EXCLUDE_FALSE) && (query->exclude_terms)) {
_notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsigned *count_out)
{
notmuch_database_t *notmuch = query->notmuch;
- const char *query_string = query->query_string;
Xapian::doccount count = 0;
notmuch_status_t status;
Xapian::Query final_query, exclude_query;
Xapian::MSet mset;
- if (strcmp (query_string, "") == 0 ||
- strcmp (query_string, "*") == 0) {
- final_query = mail_query;
- } else {
- final_query = Xapian::Query (Xapian::Query::OP_AND,
- mail_query, query->xapian_query);
- }
+ final_query = Xapian::Query (Xapian::Query::OP_AND,
+ mail_query, query->xapian_query);
exclude_query = _notmuch_exclude_tags (query);
{
return query->notmuch;
}
+
+notmuch_status_t
+_notmuch_query_expand (notmuch_database_t *notmuch, const char *field, Xapian::Query subquery,
+ Xapian::Query &output, std::string &msg)
+{
+ std::set<std::string> terms;
+ const std::string term_prefix = _find_prefix (field);
+
+ if (_debug_query ()) {
+ fprintf (stderr, "Expanding subquery:\n%s\n",
+ subquery.get_description ().c_str ());
+ }
+
+ try {
+ Xapian::Enquire enquire (*notmuch->xapian_db);
+ Xapian::MSet mset;
+
+ enquire.set_weighting_scheme (Xapian::BoolWeight ());
+ enquire.set_query (subquery);
+
+ mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
+
+ for (Xapian::MSetIterator iterator = mset.begin (); iterator != mset.end (); iterator++) {
+ Xapian::docid doc_id = *iterator;
+ Xapian::Document doc = notmuch->xapian_db->get_document (doc_id);
+ Xapian::TermIterator i = doc.termlist_begin ();
+
+ for (i.skip_to (term_prefix);
+ i != doc.termlist_end () && ((*i).rfind (term_prefix, 0) == 0); i++) {
+ terms.insert (*i);
+ }
+ }
+ output = Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
+ if (_debug_query ()) {
+ fprintf (stderr, "Expanded query:\n%s\n",
+ subquery.get_description ().c_str ());
+ }
+
+ } catch (const Xapian::Error &error) {
+ _notmuch_database_log (notmuch,
+ "A Xapian exception occurred expanding query: %s\n",
+ error.get_msg ().c_str ());
+ msg = error.get_msg ();
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
#include "notmuch-private.h"
#include "database-private.h"
-static void
-compile_regex (regex_t ®exp, const char *str)
+notmuch_status_t
+compile_regex (regex_t ®exp, const char *str, std::string &msg)
{
int err = regcomp (®exp, str, REG_EXTENDED | REG_NOSUB);
if (err != 0) {
size_t len = regerror (err, ®exp, NULL, 0);
char *buffer = new char[len];
- std::string msg = "Regexp error: ";
+ msg = "Regexp error: ";
(void) regerror (err, ®exp, buffer, len);
msg.append (buffer, len);
delete[] buffer;
- throw Xapian::QueryParserError (msg);
+ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
+ return NOTMUCH_STATUS_SUCCESS;
}
RegexpPostingSource::RegexpPostingSource (Xapian::valueno slot, const std::string ®exp)
: slot_ (slot)
{
- compile_regex (regexp_, regexp.c_str ());
+ std::string msg;
+ notmuch_status_t status = compile_regex (regexp_, regexp.c_str (), msg);
+
+ if (status)
+ throw Xapian::QueryParserError (msg);
}
RegexpPostingSource::~RegexpPostingSource ()
return Xapian::BAD_VALUENO;
}
-RegexpFieldProcessor::RegexpFieldProcessor (std::string prefix,
+RegexpFieldProcessor::RegexpFieldProcessor (std::string field_,
notmuch_field_flag_t options_,
Xapian::QueryParser &parser_,
notmuch_database_t *notmuch_)
- : slot (_find_slot (prefix)),
- term_prefix (_find_prefix (prefix.c_str ())),
+ : slot (_find_slot (field_)),
+ field (field_),
+ term_prefix (_find_prefix (field_.c_str ())),
options (options_),
parser (parser_),
notmuch (notmuch_)
{
};
+notmuch_status_t
+_notmuch_regexp_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
+ std::string regexp_str,
+ Xapian::Query &output, std::string &msg)
+{
+ regex_t regexp;
+ notmuch_status_t status;
+
+ status = compile_regex (regexp, regexp_str.c_str (), msg);
+ if (status) {
+ _notmuch_database_log_append (notmuch, "error compiling regex %s", msg.c_str ());
+ return status;
+ }
+
+ if (slot == Xapian::BAD_VALUENO)
+ slot = _find_slot (field);
+
+ if (slot == Xapian::BAD_VALUENO) {
+ std::string term_prefix = _find_prefix (field.c_str ());
+ std::vector<std::string> terms;
+
+ for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
+ it != notmuch->xapian_db->allterms_end (); ++it) {
+ if (regexec (®exp, (*it).c_str () + term_prefix.size (),
+ 0, NULL, 0) == 0)
+ terms.push_back (*it);
+ }
+ output = Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
+ } else {
+ RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
+ output = Xapian::Query (postings->release ());
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
Xapian::Query
RegexpFieldProcessor::operator() (const std::string & str)
{
if (str.at (0) == '/') {
if (str.length () > 1 && str.at (str.size () - 1) == '/') {
+ Xapian::Query query;
std::string regexp_str = str.substr (1, str.size () - 2);
- if (slot != Xapian::BAD_VALUENO) {
- RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
- return Xapian::Query (postings->release ());
- } else {
- std::vector<std::string> terms;
- regex_t regexp;
-
- compile_regex (regexp, regexp_str.c_str ());
- for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
- it != notmuch->xapian_db->allterms_end (); ++it) {
- if (regexec (®exp, (*it).c_str () + term_prefix.size (),
- 0, NULL, 0) == 0)
- terms.push_back (*it);
- }
- return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
- }
+ std::string msg;
+ notmuch_status_t status;
+
+ status = _notmuch_regexp_to_query (notmuch, slot, field, regexp_str, query, msg);
+ if (status)
+ throw Xapian::QueryParserError (msg);
+ return query;
} else {
throw Xapian::QueryParserError ("unmatched regex delimiter in '" + str + "'");
}
#include "database-private.h"
#include "notmuch-private.h"
+notmuch_status_t
+_notmuch_regex_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
+ std::string regexp_str,
+ Xapian::Query &output, std::string &msg);
+
/* A posting source that returns documents where a value matches a
* regexp.
*/
class RegexpFieldProcessor : public Xapian::FieldProcessor {
protected:
Xapian::valueno slot;
+ std::string field;
std::string term_prefix;
notmuch_field_flag_t options;
Xapian::QueryParser &parser;
if (str.size () <= 1 || str.at (str.size () - 1) != '}') {
throw Xapian::QueryParserError ("missing } in '" + str + "'");
} else {
+ Xapian::Query subquery;
+ Xapian::Query query;
+ std::string msg;
std::string subquery_str = str.substr (1, str.size () - 2);
- notmuch_query_t *subquery = notmuch_query_create (notmuch, subquery_str.c_str ());
- notmuch_messages_t *messages;
- std::set<std::string> terms;
- if (! subquery)
- throw Xapian::QueryParserError ("failed to create subquery for '" + subquery_str +
- "'");
+ status = _notmuch_query_string_to_xapian_query (notmuch, subquery_str, subquery, msg);
+ if (status)
+ throw Xapian::QueryParserError (msg);
- status = notmuch_query_search_messages (subquery, &messages);
+ status = _notmuch_query_expand (notmuch, "thread", subquery, query, msg);
if (status)
- throw Xapian::QueryParserError ("failed to search messages for '" + subquery_str +
- "'");
+ throw Xapian::QueryParserError (msg);
- for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) {
- std::string term = thread_prefix;
- notmuch_message_t *message;
- message = notmuch_messages_get (messages);
- term += _notmuch_message_get_thread_id_only (message);
- terms.insert (term);
- }
- return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
+ return query;
}
} else {
/* literal thread id */
#include "command-line-arguments.h"
-extern const char *notmuch_requested_db_uuid;
extern const notmuch_opt_desc_t notmuch_shared_options [];
-void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
-void notmuch_process_shared_options (const char *subcommand_name);
+notmuch_query_syntax_t shared_option_query_syntax ();
+
+void notmuch_process_shared_options (notmuch_database_t *notmuch, const char *subcommand_name);
int notmuch_minimal_options (const char *subcommand_name,
int argc, char **argv);
if (opt_index < 0)
return EXIT_FAILURE;
- if (notmuch_requested_db_uuid) {
- fprintf (stderr, "Error: --uuid not implemented for compact\n");
- return EXIT_FAILURE;
- }
-
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (NULL, argv[0]);
if (! quiet)
printf ("Compacting database...\n");
{ "index.decrypt", false, NULL },
{ "index.header.", true, validate_field_name },
{ "query.", true, NULL },
+ { "squery.", true, validate_field_name },
};
static const config_key_info_t *
printf ("%sretry_lock=%s\n",
BUILT_WITH_PREFIX,
notmuch_built_with ("retry_lock") ? "true" : "false");
+ printf ("%ssexpr_query=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("sexpr_query") ? "true" : "false");
}
static int
if (opt_index < 0)
return EXIT_FAILURE;
- if (notmuch_requested_db_uuid)
- fprintf (stderr, "Warning: ignoring --uuid=%s\n",
- notmuch_requested_db_uuid);
-
/* skip at least subcommand argument */
argc -= opt_index;
argv += opt_index;
int ret = 0;
notmuch_status_t status;
- query = notmuch_query_create (notmuch, query_str);
- if (query == NULL) {
- fprintf (stderr, "Out of memory\n");
- return -1;
+ status = notmuch_query_create_with_syntax (notmuch, query_str,
+ shared_option_query_syntax (),
+ &query);
+ if (print_status_database ("notmuch count", notmuch, status)) {
+ ret = -1;
+ goto DONE;
}
for (notmuch_config_values_start (exclude_tags);
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
if (input_file_name) {
batch = true;
return EXIT_FAILURE;
}
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
if (! query_str)
query_str = "";
- query = notmuch_query_create (notmuch, query_str);
- if (query == NULL) {
- fprintf (stderr, "Out of memory\n");
+ status = notmuch_query_create_with_syntax (notmuch, query_str,
+ shared_option_query_syntax (),
+ &query);
+ if (print_status_database ("notmuch dump", notmuch, status))
return EXIT_FAILURE;
- }
+
/* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the
* first results quickly at the expense of total time.
*/
const char *query_str = NULL;
int ret;
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
const char *output_file_name = NULL;
int opt_index;
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
if (include == 0)
include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS | DUMP_INCLUDE_PROPERTIES;
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
mail_root = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
return EXIT_FAILURE;
}
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
status = notmuch_process_shared_indexing_options (notmuch);
if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to process index options. (%s)\n",
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
/* quiet trumps verbose */
if (quiet)
return EXIT_FAILURE;
}
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
if (notmuch_database_get_revision (notmuch, NULL) == 0) {
int count = 0;
count_files (mail_root, &count, &add_files_state);
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
- query = notmuch_query_create (notmuch, query_string);
- if (query == NULL) {
- fprintf (stderr, "Out of memory.\n");
+ status = notmuch_query_create_with_syntax (notmuch, query_string,
+ shared_option_query_syntax (),
+ &query);
+ if (print_status_database ("notmuch reindex", notmuch, status))
return 1;
- }
/* reindexing is not interested in any special sort order */
notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
-
- notmuch_exit_if_unmatched_db_uuid (notmuch);
+ notmuch_process_shared_options (notmuch, argv[0]);
status = notmuch_process_shared_indexing_options (notmuch);
if (status != NOTMUCH_STATUS_SUCCESS) {
* (last Received: header added) and try to extract from them
* indications to which email address this message was delivered.
*
- * The Received: header is special in our get_header function and is
- * always concatenated.
+ * The Received: header is among special ones in our get_header function
+ * and is always concatenated.
*
* Return the address that was found, if any, and NULL otherwise.
*/
* headers: Envelope-To, X-Original-To, and Delivered-To (searched in
* that order).
*
+ * The Delivered-To: header is among special ones in our get_header
+ * function and is always concatenated.
+ *
* Return the address that was found, if any, and NULL otherwise.
*/
static const char *
};
int format = FORMAT_DEFAULT;
int reply_all = true;
+ notmuch_status_t status;
notmuch_opt_desc_t options[] = {
{ .opt_keyword = &format, .name = "format", .keywords =
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
notmuch_exit_if_unsupported_format ();
return EXIT_FAILURE;
}
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
- query = notmuch_query_create (notmuch, query_string);
- if (query == NULL) {
- fprintf (stderr, "Out of memory\n");
+ status = notmuch_query_create_with_syntax (notmuch, query_string,
+ shared_option_query_syntax (),
+ &query);
+ if (print_status_database ("notmuch reply", notmuch, status))
return EXIT_FAILURE;
- }
if (do_reply (notmuch, query, ¶ms, format, reply_all) != 0)
return EXIT_FAILURE;
goto DONE;
}
- notmuch_process_shared_options (argv[0]);
- notmuch_exit_if_unmatched_db_uuid (notmuch);
+ notmuch_process_shared_options (notmuch, argv[0]);
if (include == 0) {
include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES | DUMP_INCLUDE_TAGS;
int format_sel;
sprinter_t *format;
int exclude;
+ int query_syntax;
notmuch_query_t *query;
int sort;
int output;
notmuch_exit_if_unsupported_format ();
- notmuch_exit_if_unmatched_db_uuid (ctx->notmuch);
-
query_str = query_string_from_args (ctx->notmuch, argc, argv);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
- ctx->query = notmuch_query_create (ctx->notmuch, query_str);
- if (ctx->query == NULL) {
- fprintf (stderr, "Out of memory\n");
+ if (print_status_database ("notmuch search", ctx->notmuch,
+ notmuch_query_create_with_syntax (ctx->notmuch, query_str,
+ shared_option_query_syntax (),
+ &ctx->query)))
return EXIT_FAILURE;
- }
notmuch_query_set_sort (ctx->query, ctx->sort);
.format_sel = NOTMUCH_FORMAT_TEXT,
.exclude = NOTMUCH_EXCLUDE_TRUE,
.sort = NOTMUCH_SORT_NEWEST_FIRST,
+ .query_syntax = NOTMUCH_QUERY_SYNTAX_XAPIAN,
.output = 0,
.offset = 0,
.limit = -1, /* unlimited */
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
ctx->dupe != -1) {
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
if (! (ctx->output & (OUTPUT_SENDER | OUTPUT_RECIPIENTS)))
ctx->output |= OUTPUT_SENDER;
if (notmuch_minimal_options ("setup", argc, argv) < 0)
return EXIT_FAILURE;
- if (notmuch_requested_db_uuid)
- fprintf (stderr, "Warning: ignoring --uuid=%s\n",
- notmuch_requested_db_uuid);
-
config = notmuch_conffile_open (notmuch,
notmuch_config_path (notmuch), true);
if (! config)
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
/* explicit decryption implies verification */
if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH ||
}
}
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
query_string = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_string == NULL) {
fprintf (stderr, "Out of memory\n");
return EXIT_FAILURE;
}
- query = notmuch_query_create (notmuch, query_string);
- if (query == NULL) {
- fprintf (stderr, "Out of memory\n");
+ status = notmuch_query_create_with_syntax (notmuch, query_string,
+ shared_option_query_syntax (),
+ &query);
+ if (print_status_database ("notmuch show", notmuch, status))
return EXIT_FAILURE;
- }
notmuch_query_set_sort (query, sort);
static char *
-_optimize_tag_query (void *ctx, const char *orig_query_string,
- const tag_op_list_t *list)
+_optimize_tag_query_infix (void *ctx, const char *orig_query_string,
+ const tag_op_list_t *list)
{
/* This is subtler than it looks. Xapian ignores the '-' operator
* at the beginning both queries and parenthesized groups and,
return query_string;
}
+static char *
+_optimize_tag_query (void *ctx, const char *orig_query_string,
+ notmuch_query_syntax_t stx,
+ const tag_op_list_t *list)
+{
+ char *query_string;
+
+ if (stx == NOTMUCH_QUERY_SYNTAX_XAPIAN)
+ return _optimize_tag_query_infix (ctx, orig_query_string, list);
+
+ /* Don't optimize if there are no tag changes. */
+ if (tag_op_list_size (list) == 0)
+ return talloc_strdup (ctx, orig_query_string);
+
+ query_string = talloc_asprintf (ctx, "(and %s", orig_query_string);
+ for (size_t i = 0; i < tag_op_list_size (list) && query_string; i++) {
+ query_string = talloc_asprintf_append_buffer (
+ query_string, tag_op_list_isremove (list, i) ? " (tag \"%s\")" : " (not (tag \"%s\"))",
+ tag_op_list_tag (list, i));
+ }
+
+ if (query_string)
+ query_string = talloc_strdup_append_buffer (query_string, ")");
+
+ return query_string;
+}
+
/* Tag messages matching 'query_string' according to 'tag_ops'
*/
static int
if (! (flags & TAG_FLAG_REMOVE_ALL)) {
/* Optimize the query so it excludes messages that already
* have the specified set of tags. */
- query_string = _optimize_tag_query (ctx, query_string, tag_ops);
+ query_string = _optimize_tag_query (ctx, query_string,
+ shared_option_query_syntax (),
+ tag_ops);
if (query_string == NULL) {
fprintf (stderr, "Out of memory.\n");
return 1;
flags |= TAG_FLAG_PRE_OPTIMIZED;
}
- query = notmuch_query_create (notmuch, query_string);
- if (query == NULL) {
- fprintf (stderr, "Out of memory.\n");
+ status = notmuch_query_create_with_syntax (notmuch, query_string,
+ shared_option_query_syntax (),
+ &query);
+ if (print_status_database ("notmuch tag", notmuch, status))
return 1;
- }
/* tagging is not interested in any special sort order */
notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
if (opt_index < 0)
return EXIT_FAILURE;
- notmuch_process_shared_options (argv[0]);
+ notmuch_process_shared_options (notmuch, argv[0]);
if (input_file_name) {
batch = true;
}
}
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
if (print_status_database (
"notmuch restore",
notmuch,
static int
_help_for (const char *topic);
+static void
+notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
+
static bool print_version = false, print_help = false;
-const char *notmuch_requested_db_uuid = NULL;
+static const char *notmuch_requested_db_uuid = NULL;
+static int query_syntax = NOTMUCH_QUERY_SYNTAX_XAPIAN;
const notmuch_opt_desc_t notmuch_shared_options [] = {
{ .opt_bool = &print_version, .name = "version" },
{ .opt_bool = &print_help, .name = "help" },
{ .opt_string = ¬much_requested_db_uuid, .name = "uuid" },
+ { .opt_keyword = &query_syntax, .name = "query", .keywords =
+ (notmuch_keyword_t []){ { "infix", NOTMUCH_QUERY_SYNTAX_XAPIAN },
+ { "sexp", NOTMUCH_QUERY_SYNTAX_SEXP },
+ { 0, 0 } } },
+
{ }
};
+notmuch_query_syntax_t
+shared_option_query_syntax ()
+{
+ return query_syntax;
+}
+
/* any subcommand wanting to support these options should call
* inherit notmuch_shared_options and call
- * notmuch_process_shared_options (subcommand_name);
+ * notmuch_process_shared_options (notmuch, subcommand_name);
+ * with notmuch = an open database, or NULL.
*/
void
-notmuch_process_shared_options (const char *subcommand_name)
+notmuch_process_shared_options (notmuch_database_t *notmuch, const char *subcommand_name)
{
if (print_version) {
printf ("notmuch " STRINGIFY (NOTMUCH_VERSION) "\n");
int ret = _help_for (subcommand_name);
exit (ret);
}
+
+ if (notmuch) {
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+ } else {
+ if (notmuch_requested_db_uuid)
+ fprintf (stderr, "Warning: ignoring --uuid=%s\n",
+ notmuch_requested_db_uuid);
+ }
}
/* This is suitable for subcommands that do not actually open the
return -1;
/* We can't use argv here as it is sometimes NULL */
- notmuch_process_shared_options (subcommand_name);
+ notmuch_process_shared_options (NULL, subcommand_name);
return opt_index;
}
}
}
-void
+static void
notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
{
const char *uuid = NULL;
if (opt_index < argc)
command_name = argv[opt_index];
- notmuch_process_shared_options (command_name);
+ notmuch_process_shared_options (NULL, command_name);
command = find_command (command_name);
/* if command->function is NULL, try external command */
built_with.compact=something
built_with.field_processor=something
built_with.retry_lock=something
+built_with.sexpr_query=something
database.autocommit=8000
database.mail_root=MAIL_DIR
database.path=MAIL_DIR
test_expect_equal "${output}+${output2}" "${value}+"
test_begin_subtest "Config list ($config)"
- notmuch config list | notmuch_dir_sanitize | \
+ notmuch config list | notmuch_config_sanitize | \
sed -e "s/^database.backup_dir=.*$/database.backup_dir/" \
-e "s/^database.hook_dir=.*$/database.hook_dir/" \
-e "s/^database.path=.*$/database.path/" \
-e "s,^database.mail_root=CWD/home/env_points_here,database.mail_root=MAIL_DIR," \
> OUTPUT
cat <<EOF > EXPECTED
-built_with.compact=true
-built_with.field_processor=true
-built_with.retry_lock=true
+built_with.compact=something
+built_with.field_processor=something
+built_with.retry_lock=something
+built_with.sexpr_query=something
database.autocommit=8000
database.backup_dir
database.hook_dir
output2=$(notmuch --config='' config get ${key})
notmuch config set ${key}
test_expect_equal "${output}+${output2}" "${value}+"
- ;;
+ ;&
+ split)
+ test_begin_subtest "'to' header does not crash (python-cffi) ($config)"
+ echo 'notmuch@notmuchmail.org' > EXPECTED
+ test_python <<EOF
+from notmuch2 import Database
+db=Database(config=Database.CONFIG.SEARCH)
+m=db.find('20091117232137.GA7669@griffis1.net')
+to=m.header('To')
+print(to)
+EOF
+ test_expect_equal_file EXPECTED OUTPUT
+ ;& # fall through
+ esac
+
+ case $config in
+ split|XDG*)
esac
restore_config
rm -rf home/.local
EOF
test_expect_equal_file EXPECTED OUTPUT
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+
+ test_begin_subtest "and of exact terms (query=sexp)"
+ output=$(notmuch count --query=sexp '(and "wonderful" "wizard")')
+ test_expect_equal "$output" 1
+
+ test_begin_subtest "or of exact terms (query=sexp)"
+ output=$(notmuch count --query=sexp '(or "php" "wizard")')
+ test_expect_equal "$output" 2
+
+ test_begin_subtest "starts-with, case-insensitive (query=sexp)"
+ output=$(notmuch count --query=sexp '(starts-with FreeB)')
+ test_expect_equal "$output" 5
+
+ test_begin_subtest "query that matches no messages (query=sexp)"
+ count=$(notmuch count --query=sexp '(and (from keithp) (to keithp))')
+ test_expect_equal 0 "$count"
+
+ test_begin_subtest "Compound subquery (query=sexp)"
+ output=$(notmuch count --query=sexp '(thread (of (from keithp) (subject Maildir)))')
+ test_expect_equal "$output" 7
+
+fi
+
test_done
--- /dev/null
+#!/usr/bin/env bash
+test_description='"notmuch search" in several variations'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+if [ $NOTMUCH_HAVE_SFSEXP -ne 1 ]; then
+ printf "Skipping due to missing sfsexp library\n"
+ test_done
+fi
+
+add_email_corpus
+
+for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
+ '(or (and) (or) (not (and)))'; do
+ test_begin_subtest "all messages: $query"
+ notmuch search '*' > EXPECTED
+ notmuch search --query=sexp "$query" > OUTPUT
+ test_expect_equal_file EXPECTED OUTPUT
+done
+
+for query in '(or)' '(not ())' '(not (not))' '(not (and))' \
+ '(not (or (and) (or) (not (and))))'; do
+ test_begin_subtest "no messages: $query"
+ notmuch search --query=sexp "$query" > OUTPUT
+ test_expect_equal_file /dev/null OUTPUT
+done
+
+test_begin_subtest "and of exact terms"
+notmuch search --query=sexp '(and "wonderful" "wizard")' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "or of exact terms"
+notmuch search --query=sexp '(or "php" "wizard")' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2010-12-29 [1/1] François Boulogne; [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "single term in body"
+notmuch search --query=sexp 'wizard' | notmuch_search_sanitize>OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "single term in body (case insensitive)"
+notmuch search --query=sexp 'Wizard' | notmuch_search_sanitize>OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "single term in body, stemmed version"
+notmuch search arriv > EXPECTED
+notmuch search --query=sexp arriv > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "single term in body, unstemmed version"
+notmuch search --query=sexp '"arriv"' > OUTPUT
+test_expect_equal_file /dev/null OUTPUT
+
+test_begin_subtest "Search by 'subject'"
+add_message [subject]=subjectsearchtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --query=sexp '(subject subjectsearchtest)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)"
+
+test_begin_subtest "Search by 'subject' (case insensitive)"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(subject "Maildir")' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'subject' (utf-8):"
+add_message [subject]=utf8-sübjéct '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --query=sexp '(subject utf8 sübjéct)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
+
+test_begin_subtest "Search by 'subject' (utf-8, and):"
+output=$(notmuch search --query=sexp '(subject (and utf8 sübjéct))' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
+
+test_begin_subtest "Search by 'subject' (utf-8, and outside):"
+output=$(notmuch search --query=sexp '(and (subject utf8) (subject sübjéct))' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
+
+test_begin_subtest "Search by 'subject' (utf-8, or):"
+notmuch search --query=sexp '(subject (or utf8 subjectsearchtest))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'subject' (utf-8, or outside):"
+notmuch search --query=sexp '(or (subject utf8) (subject subjectsearchtest))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'attachment'"
+notmuch search attachment:notmuch-help.patch > EXPECTED
+notmuch search --query=sexp '(attachment notmuch-help.patch)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'body'"
+add_message '[subject]="body search"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [body]=bodysearchtest
+output=$(notmuch search --query=sexp '(body bodysearchtest)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; body search (inbox unread)"
+
+test_begin_subtest "Search by 'body' (phrase)"
+add_message '[subject]="body search (phrase)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="body search (phrase)"'
+add_message '[subject]="negative result"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="This phrase should not match the body search"'
+output=$(notmuch search --query=sexp '(body "body search phrase")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; body search (phrase) (inbox unread)"
+
+test_begin_subtest "Search by 'body' (utf-8):"
+add_message '[subject]="utf8-message-body-subject"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="message body utf8: bödý"'
+output=$(notmuch search --query=sexp '(body bödý)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)"
+
+add_message "[body]=thebody-1" "[subject]=kryptonite-1"
+add_message "[body]=nothing-to-see-here-1" "[subject]=thebody-1"
+
+test_begin_subtest 'search without body: prefix'
+notmuch search thebody > EXPECTED
+notmuch search --query=sexp '(and thebody)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'negated body: prefix'
+notmuch search thebody and not body:thebody > EXPECTED
+notmuch search --query=sexp '(and (not (body thebody)) thebody)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search unprefixed for prefixed term'
+notmuch search kryptonite > EXPECTED
+notmuch search --query=sexp '(and kryptonite)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'search with body: prefix for term only in subject'
+notmuch search body:kryptonite > EXPECTED
+notmuch search --query=sexp '(body kryptonite)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'from'"
+add_message '[subject]="search by from"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [from]=searchbyfrom
+output=$(notmuch search --query=sexp '(from searchbyfrom)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] searchbyfrom; search by from (inbox unread)"
+
+test_begin_subtest "Search by 'from' (address)"
+add_message '[subject]="search by from (address)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [from]=searchbyfrom@example.com
+output=$(notmuch search --query=sexp '(from searchbyfrom@example.com)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] searchbyfrom@example.com; search by from (address) (inbox unread)"
+
+test_begin_subtest "Search by 'from' (name)"
+add_message '[subject]="search by from (name)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[from]="Search By From Name <test@example.com>"'
+output=$(notmuch search --query=sexp '(from "Search By From Name")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
+
+test_begin_subtest "Search by 'from' (name and address)"
+output=$(notmuch search --query=sexp '(from "Search By From Name <test@example.com>")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
+
+add_message '[dir]=bad' '[subject]="To the bone"'
+add_message '[dir]=.' '[subject]="Top level"'
+add_message '[dir]=bad/news' '[subject]="Bears"'
+mkdir -p "${MAIL_DIR}/duplicate/bad/news"
+cp "$gen_msg_filename" "${MAIL_DIR}/duplicate/bad/news"
+
+add_message '[dir]=things' '[subject]="These are a few"'
+add_message '[dir]=things/favorite' '[subject]="Raindrops, whiskers, kettles"'
+add_message '[dir]=things/bad' '[subject]="Bites, stings, sad feelings"'
+
+test_begin_subtest "Search by 'folder' (multiple)"
+output=$(notmuch search --query=sexp '(folder bad bad/news things/bad)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
+thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
+
+test_begin_subtest "Search by 'folder': top level."
+notmuch search folder:'""' > EXPECTED
+notmuch search --query=sexp '(folder "")' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'id'"
+add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --query=sexp "(id ${gen_msg_id})" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
+
+test_begin_subtest "Search by 'id' (or)"
+add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --query=sexp "(id non-existent-mid ${gen_msg_id})" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
+
+test_begin_subtest "Search by 'is' (multiple)"
+notmuch tag -inbox tag:searchbytag
+notmuch search is:inbox AND is:unread | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(is inbox unread)' | notmuch_search_sanitize > OUTPUT
+notmuch tag +inbox tag:searchbytag
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'mid'"
+add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --query=sexp "(mid ${gen_msg_id})" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)"
+
+test_begin_subtest "Search by 'mid' (or)"
+add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search --query=sexp "(mid non-existent-mid ${gen_msg_id})" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)"
+
+test_begin_subtest "Search by 'mimetype'"
+notmuch search mimetype:text/html > EXPECTED
+notmuch search --query=sexp '(mimetype text html)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
+QUERYSTR2="query:test and subject:Maildir"
+notmuch config set --database query.test "$QUERYSTR"
+notmuch config set query.test2 "$QUERYSTR2"
+
+test_begin_subtest "ill-formed named query search"
+notmuch search --query=sexp '(query)' > OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'query' expects single atom as argument
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "ill-formed named query search 2"
+notmuch search --query=sexp '(to (query))' > OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'query' not supported inside 'to'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search named query"
+notmuch search --query=sexp '(query test)' > OUTPUT
+notmuch search $QUERYSTR > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'subject' (utf-8, phrase-token):"
+output=$(notmuch search --query=sexp '(subject utf8-sübjéct)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
+
+test_begin_subtest "search named query with other terms"
+notmuch search --query=sexp '(and (query test) (subject Maildir))' > OUTPUT
+notmuch search $QUERYSTR and subject:Maildir > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search nested named query"
+notmuch search --query=sexp '(query test2)' > OUTPUT
+notmuch search $QUERYSTR2 > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'subject' (utf-8, quoted string):"
+output=$(notmuch search --query=sexp '(subject "utf8 sübjéct")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
+
+test_begin_subtest "Search by 'subject' (combine phrase, term):"
+output=$(notmuch search --query=sexp '(subject Mac "compatibility issues")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
+
+test_begin_subtest "Search by 'subject' (combine phrase, term 2):"
+notmuch search --query=sexp '(subject (or utf8 "compatibility issues"))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'subject' (combine phrase, term 3):"
+notmuch search --query=sexp '(subject issues X/Darwin)' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'tag'"
+add_message '[subject]="search by tag"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+notmuch tag +searchbytag id:${gen_msg_id}
+output=$(notmuch search --query=sexp '(tag searchbytag)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)"
+
+test_begin_subtest "Search by 'tag' (multiple)"
+notmuch tag -inbox tag:searchbytag
+notmuch search tag:inbox AND tag:unread | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(tag inbox unread)' | notmuch_search_sanitize > OUTPUT
+notmuch tag +inbox tag:searchbytag
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'tag' and 'subject'"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(and (tag inbox) (subject maildir))' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search by 'thread'"
+add_message '[subject]="search by thread"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+thread_id=$(notmuch search id:${gen_msg_id} | sed -e "s/thread:\([a-f0-9]*\).*/\1/")
+output=$(notmuch search --query=sexp "(thread ${thread_id})" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)"
+
+test_begin_subtest "Search by 'to'"
+add_message '[subject]="search by to"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [to]=searchbyto
+output=$(notmuch search --query=sexp '(to searchbyto)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)"
+
+test_begin_subtest "Search by 'to' (address)"
+add_message '[subject]="search by to (address)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [to]=searchbyto@example.com
+output=$(notmuch search --query=sexp '(to searchbyto@example.com)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (address) (inbox unread)"
+
+test_begin_subtest "Search by 'to' (name)"
+add_message '[subject]="search by to (name)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[to]="Search By To Name <test@example.com>"'
+output=$(notmuch search --query=sexp '(to "Search By To Name")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
+
+test_begin_subtest "Search by 'to' (name and address)"
+output=$(notmuch search --query=sexp '(to "Search By To Name <test@example.com>")' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
+
+test_begin_subtest "starts-with, no prefix"
+output=$(notmuch search --query=sexp '(starts-with prelim)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)"
+
+test_begin_subtest "starts-with, case-insensitive"
+notmuch search --query=sexp '(starts-with FreeB)' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [3/4] Alexander Botero-Lowry, Jjgod Jiang; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+thread:XXX 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, no prefix, all messages"
+notmuch search --query=sexp '(starts-with "")' | notmuch_search_sanitize > OUTPUT
+notmuch search '*' | notmuch_search_sanitize > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, attachment"
+output=$(notmuch search --query=sexp '(attachment (starts-with not))' | notmuch_search_sanitize)
+test_expect_equal "$output" 'thread:XXX 2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)'
+
+test_begin_subtest "starts-with, folder"
+notmuch search --output=files --query=sexp '(folder (starts-with bad))' | notmuch_dir_sanitize | sed 's/[0-9]*$/XXX/' > OUTPUT
+cat <<EOF > EXPECTED
+MAIL_DIR/bad/msg-XXX
+MAIL_DIR/bad/news/msg-XXX
+MAIL_DIR/duplicate/bad/news/msg-XXX
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, from"
+notmuch search --query=sexp '(from (starts-with Mik))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-17 [1/1] Mikhail Gusarov; [notmuch] [PATCH] Handle rename of message file (inbox unread)
+thread:XXX 2009-11-17 [2/7] Mikhail Gusarov| Lars Kellogg-Stedman, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+thread:XXX 2009-11-17 [2/5] Mikhail Gusarov| Carl Worth, Keith Packard; [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, id"
+notmuch search --query=sexp --output=messages '(id (starts-with 877))' > OUTPUT
+cat <<EOF > EXPECTED
+id:877h1wv7mg.fsf@inf-8657.int-evry.fr
+id:877htoqdbo.fsf@yoom.home.cworth.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, is"
+output=$(notmuch search --query=sexp '(is (starts-with searchby))' | notmuch_search_sanitize)
+test_expect_equal "$output" 'thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)'
+
+test_begin_subtest "starts-with, mid"
+notmuch search --query=sexp --output=messages '(mid (starts-with 877))' > OUTPUT
+cat <<EOF > EXPECTED
+id:877h1wv7mg.fsf@inf-8657.int-evry.fr
+id:877htoqdbo.fsf@yoom.home.cworth.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, mimetype"
+notmuch search --query=sexp '(mimetype (starts-with sig))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+thread:XXX 2009-11-18 [4/7] Lars Kellogg-Stedman, Mikhail Gusarov| Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+thread:XXX 2009-11-17 [1/3] Adrian Perez de Castro| Keith Packard, Carl Worth; [notmuch] Introducing myself (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+add_message '[subject]="message with properties"'
+notmuch restore <<EOF
+#= ${gen_msg_id} foo=bar
+EOF
+
+test_begin_subtest "starts-with, property"
+notmuch search --query=sexp '(property (starts-with foo=))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; message with properties (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, subject"
+notmuch search --query=sexp '(subject (starts-with FreeB))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, tag"
+output=$(notmuch search --query=sexp '(tag (starts-with searchby))' | notmuch_search_sanitize)
+test_expect_equal "$output" 'thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)'
+
+add_message '[subject]="no tags"'
+notag_mid=${gen_msg_id}
+notmuch tag -unread -inbox id:${notag_mid}
+
+test_begin_subtest "negated starts-with, tag"
+output=$(notmuch search --query=sexp '(tag (not (starts-with in)))' | notmuch_search_sanitize)
+test_expect_equal "$output" 'thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; no tags ()'
+
+test_begin_subtest "negated starts-with, tag 2"
+output=$(notmuch search --query=sexp '(not (tag (starts-with in)))' | notmuch_search_sanitize)
+test_expect_equal "$output" 'thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; no tags ()'
+
+test_begin_subtest "negated starts-with, tag 3"
+output=$(notmuch search --query=sexp '(not (tag (starts-with "")))' | notmuch_search_sanitize)
+test_expect_equal "$output" 'thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; no tags ()'
+
+test_begin_subtest "starts-with, thread"
+notmuch search --query=sexp '(thread (starts-with "00"))' > OUTPUT
+notmuch search '*' > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, to"
+notmuch search --query=sexp '(to (starts-with "search"))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (address) (inbox unread)
+thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "wildcard search for 'is'"
+notmuch search not id:${notag_mid} > EXPECTED
+notmuch search --query=sexp '(is *)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "negated wildcard search for 'is'"
+notmuch search id:${notag_mid} > EXPECTED
+notmuch search --query=sexp '(not (is *))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "wildcard search for 'property'"
+notmuch search property:foo=bar > EXPECTED
+notmuch search --query=sexp '(property *)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "wildcard search for 'tag'"
+notmuch search not id:${notag_mid} > EXPECTED
+notmuch search --query=sexp '(tag *)' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "negated wildcard search for 'tag'"
+notmuch search id:${notag_mid} > EXPECTED
+notmuch search --query=sexp '(not (tag *))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+add_message '[subject]="message with tag \"*\""'
+notmuch tag '+*' id:${gen_msg_id}
+
+test_begin_subtest "search for 'tag' \"*\""
+output=$(notmuch search --query=sexp --output=messages '(tag "*")')
+test_expect_equal "$output" "id:$gen_msg_id"
+
+test_begin_subtest "search for missing / empty to"
+add_message [to]="undisclosed-recipients:"
+notmuch search --query=sexp '(not (to *))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; search for missing / empty to (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Unbalanced parens"
+# A code 1 indicates the error was handled (a crash will return e.g. 139).
+test_expect_code 1 "notmuch search --query=sexp '('"
+
+test_begin_subtest "Unbalanced parens, error message"
+notmuch search --query=sexp '(' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+invalid s-expression: '('
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "unknown prefix"
+notmuch search --query=sexp '(foo)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+unknown prefix 'foo'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "list as prefix"
+notmuch search --query=sexp '((foo))' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+unexpected list in field/operation position
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "illegal nesting"
+notmuch search --query=sexp '(subject (subject foo))' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+nested field: 'subject' inside 'subject'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, no argument"
+notmuch search --query=sexp '(starts-with)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'starts-with' expects single atom as argument
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, list argument"
+notmuch search --query=sexp '(starts-with (stuff))' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'starts-with' expects single atom as argument
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, too many arguments"
+notmuch search --query=sexp '(starts-with a b c)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'starts-with' expects single atom as argument
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "starts-with, illegal field"
+notmuch search --query=sexp '(body (starts-with foo))' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'body' does not support wildcard queries
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "wildcard, illegal field"
+notmuch search --query=sexp '(body *)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'body' does not support wildcard queries
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search, exclude \"deleted\" messages from search"
+notmuch config set search.exclude_tags deleted
+generate_message '[subject]="Not deleted"'
+not_deleted_id=$gen_msg_id
+generate_message '[subject]="Deleted"'
+notmuch new > /dev/null
+notmuch tag +deleted id:$gen_msg_id
+deleted_id=$gen_msg_id
+output=$(notmuch search --query=sexp '(subject deleted)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)"
+
+test_begin_subtest "Search, exclude \"deleted\" messages from message search --exclude=false"
+output=$(notmuch search --query=sexp --exclude=false --output=messages '(subject deleted)' | notmuch_search_sanitize)
+test_expect_equal "$output" "id:$not_deleted_id
+id:$deleted_id"
+
+test_begin_subtest "Search, exclude \"deleted\" messages from search, overridden"
+notmuch search --query=sexp '(and (subject deleted) (tag deleted))' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Deleted (deleted inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search, exclude \"deleted\" messages from threads"
+add_message '[subject]="Not deleted reply"' '[in-reply-to]="<$gen_msg_id>"'
+output=$(notmuch search --query=sexp '(subject deleted)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)"
+
+test_begin_subtest "Search, don't exclude \"deleted\" messages when --exclude=flag specified"
+output=$(notmuch search --query=sexp --exclude=flag '(subject deleted)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+thread:XXX 2001-01-05 [1/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
+
+test_begin_subtest "Search, don't exclude \"deleted\" messages from search if not configured"
+notmuch config set search.exclude_tags
+output=$(notmuch search --query=sexp '(subject deleted)' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+thread:XXX 2001-01-05 [2/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
+
+test_begin_subtest "regex at top level"
+notmuch search --query=sexp '(rx foo)' >& OUTPUT
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+illegal 'rx' outside field
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regex in illegal field"
+notmuch search --query=sexp '(body (regex foo))' >& OUTPUT
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'regex' not supported in field 'body'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+notmuch search --output=messages from:cworth > cworth.msg-ids
+
+test_begin_subtest "regexp 'from' search"
+notmuch search --output=messages --query=sexp '(from (rx cworth))' > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "regexp search for 'from' 2"
+notmuch search from:/cworth@cworth.org/ and subject:patch | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(and (from (rx cworth@cworth.org)) (subject patch))' \
+ | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp 'folder' search"
+notmuch search 'folder:/^bar$/' | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(folder (rx ^bar$))' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp 'id' search"
+notmuch search --output=messages --query=sexp '(id (rx yoom))' > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "unanchored 'is' search"
+notmuch search tag:signed or tag:inbox > EXPECTED
+notmuch search --query=sexp '(is (rx i))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "anchored 'is' search"
+notmuch search tag:signed > EXPECTED
+notmuch search --query=sexp '(is (rx ^si))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "combine regexp mid and subject"
+notmuch search subject:/-C/ and mid:/y..m/ | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(and (subject (rx -C)) (mid (rx y..m)))' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp 'path' search"
+notmuch search 'path:/^bar$/' | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(path (rx ^bar$))' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp 'property' search"
+notmuch search property:foo=bar > EXPECTED
+notmuch search --query=sexp '(property (rx foo=.*))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "anchored 'tag' search"
+notmuch search tag:signed > EXPECTED
+notmuch search --query=sexp '(tag (rx ^si))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp 'thread' search"
+notmuch search --output=threads '*' | grep '7$' > EXPECTED
+notmuch search --output=threads --query=sexp '(thread (rx 7$))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Basic query that matches no messages"
+count=$(notmuch count from:keithp and to:keithp)
+test_expect_equal 0 "$count"
+
+test_begin_subtest "Same query against threads"
+notmuch search --query=sexp '(and (thread (of (from keithp))) (thread (matching (to keithp))))' \
+ | notmuch_search_sanitize > OUTPUT
+cat<<EOF > EXPECTED
+thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Mix thread and non-threads query"
+notmuch search --query=sexp '(and (thread (matching keithp)) (to keithp))' | notmuch_search_sanitize > OUTPUT
+cat<<EOF > EXPECTED
+thread:XXX 2009-11-18 [1/7] Lars Kellogg-Stedman| Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Compound subquery"
+notmuch search --query=sexp '(thread (of (from keithp) (subject Maildir)))' | notmuch_search_sanitize > OUTPUT
+cat<<EOF > EXPECTED
+thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "empty subquery"
+notmuch search --query=sexp '(thread (of))' 1>OUTPUT 2>&1
+notmuch search '*' > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "illegal expansion"
+notmuch search --query=sexp '(id (of ego))' 1>OUTPUT 2>&1
+cat<<EOF > EXPECTED
+notmuch search: Syntax error in query
+'of' unsupported inside 'id'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "(folder (of subquery))"
+notmuch search --query=sexp --output=messages '(folder (of (id yun3a4cegoa.fsf@aiko.keithp.com)))' > OUTPUT
+cat <<EOF > EXPECTED
+id:yun1vjwegii.fsf@aiko.keithp.com
+id:yun3a4cegoa.fsf@aiko.keithp.com
+id:1258509400-32511-1-git-send-email-stewart@flamingspork.com
+id:1258506353-20352-1-git-send-email-stewart@flamingspork.com
+id:20091118010116.GC25380@dottiness.seas.harvard.edu
+id:20091118005829.GB25380@dottiness.seas.harvard.edu
+id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "infix query"
+notmuch search to:searchbyto | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(infix "to:searchbyto")' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "bad infix query 1"
+notmuch search --query=sexp '(infix "from:/unbalanced")' 2>&1| notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+Syntax error in infix query: from:/unbalanced
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "bad infix query 2"
+notmuch search --query=sexp '(infix "thread:{unbalanced")' 2>&1| notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+Syntax error in infix query: thread:{unbalanced
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "bad infix query 3: bad nesting"
+notmuch search --query=sexp '(subject (infix "tag:inbox"))' 2>&1| notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+'infix' not supported inside 'subject'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "infix query that matches no messages"
+notmuch search --query=sexp '(and (infix "from:keithp") (infix "to:keithp"))' > OUTPUT
+test_expect_equal_file /dev/null OUTPUT
+
+test_begin_subtest "compound infix query"
+notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED
+notmuch search --query=sexp '(infix "date:2009-11-18..2009-11-18 and tag:unread")' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "compound infix query 2"
+notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED
+notmuch search --query=sexp '(and (infix "date:2009-11-18..2009-11-18") (infix "tag:unread"))' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "user header (unknown header)"
+notmuch search --query=sexp '(FooBar)' >& OUTPUT
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+unknown prefix 'FooBar'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "adding user header"
+test_expect_code 0 "notmuch config set index.header.List \"List-Id\""
+
+test_begin_subtest "reindexing"
+test_expect_code 0 'notmuch reindex "*"'
+
+test_begin_subtest "wildcard search for user header"
+grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
+notmuch search --output=files --query=sexp '(List *)' | sort | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "wildcard search for user header 2"
+grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
+notmuch search --output=files --query=sexp '(List (starts-with not))' | sort | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search for user header"
+notmuch search List:notmuch | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(List notmuch)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search for user header (list token)"
+notmuch search List:notmuch | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(List notmuch.notmuchmail.org)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search for user header (quoted string)"
+notmuch search 'List:"notmuch notmuchmail org"' | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(List "notmuch notmuchmail org")' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search for user header (atoms)"
+notmuch search 'List:"notmuch notmuchmail org"' | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(List notmuch notmuchmail org)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "check saved query name"
+test_expect_code 1 "notmuch config set squery.test '(subject utf8-sübjéct)'"
+
+test_begin_subtest "roundtrip saved query (database)"
+notmuch config set --database squery.Test '(subject utf8-sübjéct)'
+output=$(notmuch config get squery.Test)
+test_expect_equal "$output" '(subject utf8-sübjéct)'
+
+test_begin_subtest "roundtrip saved query"
+notmuch config set squery.Test '(subject override subject)'
+output=$(notmuch config get squery.Test)
+test_expect_equal "$output" '(subject override subject)'
+
+test_begin_subtest "unknown saved query"
+notmuch search --query=sexp '(Unknown foo bar)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+unknown prefix 'Unknown'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "syntax error in saved query"
+notmuch config set squery.Bad '(Bad'
+notmuch search --query=sexp '(Bad foo bar)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+invalid saved s-expression query: '(Bad'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search by 'tag' and 'subject'"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.TagSubject '(and (tag inbox) (subject maildir))'
+notmuch search --query=sexp '(TagSubject)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: illegal nesting"
+notmuch config set squery.TagSubject '(and (tag inbox) (subject maildir))'
+notmuch search --query=sexp '(subject (TagSubject))' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+nested field: 'tag' inside 'subject'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: list as prefix"
+notmuch config set squery.Bad2 '((and) (tag inbox) (subject maildir))'
+notmuch search --query=sexp '(Bad2)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+unexpected list in field/operation position
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: bad parameter syntax"
+notmuch config set squery.Bad3 '(macro a b)'
+notmuch search --query=sexp '(Bad3)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+missing (possibly empty) list of arguments to macro
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: bad parameter syntax 2"
+notmuch config set squery.Bad4 '(macro ((a b)) a)'
+notmuch search --query=sexp '(Bad4 1)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+macro parameters must be unquoted atoms
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: bad parameter syntax 3"
+notmuch config set squery.Bad5 '(macro (a b) a)'
+notmuch search --query=sexp '(Bad5 1)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+too few arguments to macro
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: bad parameter syntax 4"
+notmuch search --query=sexp '(Bad5 1 2 3)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+too many arguments to macro
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Saved Search: macro without body"
+notmuch config set squery.Bad3 '(macro (a b))'
+notmuch search --query=sexp '(Bad3)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+missing body of macro
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "macro in query"
+notmuch search --query=sexp '(macro (a) (and ,b (subject maildir)))' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+macro definition not permitted here
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "zero argument macro"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.TagSubject0 '(macro () (and (tag inbox) (subject maildir)))'
+notmuch search --query=sexp '(TagSubject0)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "undefined argument"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.Bad6 '(macro (a) (and ,b (subject maildir)))'
+notmuch search --query=sexp '(Bad6 foo)' >OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+undefined parameter b
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Single argument macro"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.TagSubject1 '(macro (tagname) (and (tag ,tagname) (subject maildir)))'
+notmuch search --query=sexp '(TagSubject1 inbox)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Single argument macro, list argument"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.ThingSubject '(macro (thing) (and ,thing (subject maildir)))'
+notmuch search --query=sexp '(ThingSubject (tag inbox))' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "two argument macro"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.TagSubject2 '(macro (tagname subj) (and (tag ,tagname) (subject ,subj)))'
+notmuch search --query=sexp '(TagSubject2 inbox maildir)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "nested macros (shadowing)"
+notmuch search tag:inbox and subject:maildir | notmuch_search_sanitize > EXPECTED
+notmuch config set squery.Inner '(macro (x) (subject ,x))'
+notmuch config set squery.Outer '(macro (x y) (and (tag ,x) (Inner ,y)))'
+notmuch search --query=sexp '(Outer inbox maildir)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "nested macros (no dynamic scope)"
+notmuch config set squery.Inner2 '(macro (x) (subject ,y))'
+notmuch config set squery.Outer2 '(macro (x y) (and (tag ,x) (Inner2 ,y)))'
+notmuch search --query=sexp '(Outer2 inbox maildir)' > OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: Syntax error in query
+undefined parameter y
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "combine macro and user defined header"
+notmuch config set squery.About '(macro (name) (or (subject ,name) (List ,name)))'
+notmuch search subject:notmuch or List:notmuch | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(About notmuch)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "combine macro and user defined header"
+notmuch config set squery.About '(macro (name) (or (subject ,name) (List ,name)))'
+notmuch search subject:notmuch or List:notmuch | notmuch_search_sanitize > EXPECTED
+notmuch search --query=sexp '(About notmuch)' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_done
EOF
test_expect_equal_file EXPECTED OUTPUT
+if [[ NOTMUCH_HAVE_SFSEXP = 1 ]]; then
+ test_begin_subtest "sexpr query: all messages"
+ notmuch address '*' > EXPECTED
+ notmuch address --query=sexp '()' > OUTPUT
+ test_expect_equal_file EXPECTED OUTPUT
+fi
+
test_done
test_description='"notmuch tag"'
. $(dirname "$0")/test-lib.sh || exit 1
+test_query_syntax () {
+ # use a tag with a space to stress the query string munging code.
+ local new_tag="${RANDOM} ${RANDOM}"
+ test_begin_subtest "sexpr query: $1"
+ backup_database
+ notmuch tag --query=sexp "+${new_tag}" -- "$1"
+ notmuch dump > OUTPUT
+ restore_database
+ backup_database
+ notmuch tag "+${new_tag}" -- "$2"
+ notmuch dump > EXPECTED
+ restore_database
+ test_expect_equal_file_nonempty EXPECTED OUTPUT
+}
+
add_message '[subject]=One'
add_message '[subject]=Two'
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
test_expect_equal "$output" "A Xapian exception occurred opening database"
+add_email_corpus
+
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+
+ test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
+ test_query_syntax '(or "php" "wizard")' 'php or wizard'
+ test_query_syntax 'wizard' 'wizard'
+ test_query_syntax 'Wizard' 'Wizard'
+ test_query_syntax '(attachment notmuch-help.patch)' 'attachment:notmuch-help.patch'
+ test_query_syntax '(mimetype text/html)' 'mimetype:text/html'
+
+ test_begin_subtest "--batch --query=sexp"
+ notmuch dump --format=batch-tag > backup.tags
+ notmuch tag --batch --query=sexp <<EOF
+ +all -- (or One Two)
+ +none -- (and One Two)
+ EOF
+ notmuch dump > OUTPUT
+ cat <<EOF > EXPECTED
+ #notmuch-dump batch-tag:3 config,properties,tags
+ +all +inbox +tag5 +unread -- id:msg-001@notmuch-test-suite
+ +all +inbox +tag4 +tag5 +unread -- id:msg-002@notmuch-test-suite
+EOF
+ notmuch restore --format=batch-tag < backup.tags
+ test_expect_equal_file EXPECTED OUTPUT
+
+fi
+
test_done
test_description="\"notmuch reply\" in several variations"
. $(dirname "$0")/test-lib.sh || exit 1
-test_begin_subtest "Basic reply"
add_message '[from]="Sender <sender@example.com>"' \
[to]=test_suite@notmuchmail.org \
[subject]=notmuch-reply-test \
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="basic reply test"'
-output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
-test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+cat <<EOF > basic.expected
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <sender@example.com>
In-Reply-To: <${gen_msg_id}>
On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
> basic reply test
-OK"
+OK
+EOF
+
+test_begin_subtest "Basic reply"
+notmuch reply id:${gen_msg_id} >OUTPUT 2>&1 && echo OK >> OUTPUT
+test_expect_equal_file basic.expected OUTPUT
+
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+
+ test_begin_subtest "Basic reply (query=sexp)"
+ notmuch reply --query=sexp "(id ${gen_msg_id})" >OUTPUT 2>&1 && echo OK >> OUTPUT
+ test_expect_equal_file basic.expected OUTPUT
+fi
test_begin_subtest "Multiple recipients"
add_message '[from]="Sender <sender@example.com>"' \
> From guessing
OK"
+test_begin_subtest "From guessing: multiple Delivered-To"
+add_message '[from]="Sender <sender@example.com>"' \
+ '[to]="Recipient <recipient@example.com>"' \
+ '[subject]="From guessing"' \
+ '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+ '[body]="From guessing"' \
+ '[header]="Delivered-To: test_suite_other@notmuchmail.org
+Delivered-To: test_suite@notmuchmail.org"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: From guessing
+To: Sender <sender@example.com>, Recipient <recipient@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> From guessing
+OK"
+
test_begin_subtest "Reply with RFC 2047-encoded headers"
add_message '[subject]="=?iso-8859-1?q?=e0=df=e7?="' \
'[from]="=?utf-8?q?=e2=98=83?= <snowman@example.com>"' \
"crypto": {},
"date_relative": "2010-01-05",
"excluded": false,
- "filename": ["'${MAIL_DIR}'/msg-014"],
+ "filename": ["'${MAIL_DIR}'/msg-015"],
"headers": {
"Date": "Tue, 05 Jan 2010 15:43:56 +0000",
"From": "\u2603 <snowman@example.com>",
notmuch dump -- from:cworth > dump-dash-cworth.actual
test_expect_equal_file dump-cworth.expected dump-dash-cworth.actual
+
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+
+ test_begin_subtest "dump --query=sexp -- '(from cworth)'"
+ notmuch dump --query=sexp -- '(from cworth)' > dump-dash-cworth.actual2
+ test_expect_equal_file_nonempty dump-cworth.expected dump-dash-cworth.actual2
+
+ test_begin_subtest "dump --query=sexp --output=outfile '(from cworth)'"
+ notmuch dump --output=dump-outfile-cworth.actual2 --query=sexp '(from cworth)'
+ test_expect_equal_file dump-cworth.expected dump-outfile-cworth.actual2
+
+fi
+
test_begin_subtest "dump --output=outfile from:cworth"
notmuch dump --output=dump-outfile-cworth.actual from:cworth
test_expect_equal_file dump-cworth.expected dump-outfile-cworth.actual
(test-output)'
test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT
+test_begin_subtest "Functions in search-result-format"
+test_emacs '(let
+ ((notmuch-search-result-format
+ (quote ((notmuch-test-result-flags . "%s ")
+ ("date" . "%12s ")
+ ("count" . "%9s ")
+ ("authors" . "%-30s ")
+ ("subject" . "%s ")
+ ("tags" . "(%s)")))))
+ (notmuch-search "tag:inbox")
+ (notmuch-test-wait)
+ (test-output))'
+test_expect_equal_file $EXPECTED/search-result-format-function OUTPUT
+
test_begin_subtest "Incremental parsing of search results"
test_emacs "(cl-letf* (((symbol-function 'orig)
(symbol-function 'notmuch-search-process-filter))
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "notmuch-search with nonexistent CWD"
+test_emacs '(test-log-error
+ (let ((default-directory "/nonexistent"))
+ (notmuch-search "*")))'
+test_expect_equal "$(cat MESSAGES)" "COMPLETE"
+
test_done
expected='#= simple-encrypted@crypto.notmuchmail.org index.decryption=failure
#notmuch-dump batch-tag:3 config,properties,tags
+encrypted +inbox +unread -- id:basic-encrypted@crypto.notmuchmail.org
++encrypted +inbox +unread -- id:encrypted-rfc822-attachment@crypto.notmuchmail.org
+encrypted +inbox +unread -- id:encrypted-signed@crypto.notmuchmail.org
+encrypted +inbox +unread -- id:simple-encrypted@crypto.notmuchmail.org'
test_expect_equal \
fi
-test_begin_subtest "python cffi tests"
+test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
+
+test_begin_subtest "python cffi tests (NOTMUCH_CONFIG unset)"
+pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
+printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
+unset NOTMUCH_CONFIG
+test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
test_done
notmuch tag -$tag '*'
test_expect_equal_file $EXPECTED/notmuch-hello-long-names OUTPUT
+test_begin_subtest "notmuch-hello with nonexistent CWD"
+test_emacs '
+ (notmuch-hello)
+ (test-log-error
+ (let ((default-directory "/nonexistent"))
+ (notmuch-hello-update)))'
+test_expect_equal "$(cat MESSAGES)" "COMPLETE"
+
test_done
(test-visible-output)'
test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message OUTPUT
+test_begin_subtest "show encrypted rfc822 message"
+test_subtest_known_broken
+test_emacs '(notmuch-show "id:encrypted-rfc822-attachment@crypto.notmuchmail.org")
+ (test-visible-output)'
+test_expect_code 1 'fgrep "!!!" OUTPUT'
+
test_begin_subtest "show undecryptable message"
test_emacs '(notmuch-show "id:simple-encrypted@crypto.notmuchmail.org")
(test-visible-output)'
(test-visible-output))'
test_expect_equal_file $EXPECTED/notmuch-show-decrypted-message-no-crypto OUTPUT
+test_begin_subtest "notmuch-show with nonexistent CWD"
+tid=$(notmuch search --limit=1 --output=threads '*' | sed s/thread://)
+test_emacs "(test-log-error
+ (let ((default-directory \"/nonexistent\"))
+ (notmuch-show \"$tid\")))"
+test_expect_equal "$(cat MESSAGES)" "COMPLETE"
+
test_done
(notmuch-show-stash-message-id)')
test_expect_equal "$output" "\"Stashed: id:1258493565-13508-1-git-send-email-keithp@keithp.com\""
+test_begin_subtest "Functions in tree-result-format"
+test_emacs '
+(let
+ ((notmuch-tree-result-format
+ (quote (("date" . "%12s ")
+ ("authors" . "%-20s")
+ ((("tree" . "%s")
+ ("subject" . "%s")) . " %-54s ")
+ (notmuch-test-result-flags . "(%s)")))))
+ (notmuch-tree "tag:inbox")
+ (notmuch-test-wait)
+ (test-output))
+'
+test_expect_equal_file $EXPECTED/result-format-function OUTPUT
+
+test_begin_subtest "notmuch-tree with nonexistent CWD"
+test_emacs '(test-log-error
+ (let ((default-directory "/nonexistent"))
+ (notmuch-tree "*")))'
+test_expect_equal "$(cat MESSAGES)" "COMPLETE"
+
test_done
test_require_emacs
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs-unthreaded.expected-output
+
generate_message "[id]=large-thread-1" '[subject]="large thread"'
printf " 2001-01-05 Notmuch Test Suite large thread%43s(inbox unread)\n" >> EXPECTED.unthreaded
"SUCCESS")' )
test_expect_equal "$output" '"SUCCESS"'
+add_email_corpus
+test_begin_subtest "Functions in unthreaded-result-format"
+test_emacs '
+(let
+ ((notmuch-unthreaded-result-format
+ (quote (("date" . "%12s ")
+ ("authors" . "%-20s")
+ ("subject" . "%-54s")
+ (notmuch-test-result-flags . "(%s)")))))
+ (notmuch-unthreaded "tag:inbox")
+ (notmuch-test-wait)
+ (test-output))
+'
+test_expect_equal_file $EXPECTED/result-format-function OUTPUT
+
+test_begin_subtest "notmuch-unthreaded with nonexistent CWD"
+test_emacs '(test-log-error
+ (let ((default-directory "/nonexistent"))
+ (notmuch-unthreaded "*")))'
+test_expect_equal "$(cat MESSAGES)" "COMPLETE"
+
test_done
. $(dirname "$0")/test-lib.sh || exit 1
+test_query_syntax () {
+ test_begin_subtest "sexpr query: $1"
+ sexp=$(notmuch show --format=json --query=sexp "$1")
+ infix=$(notmuch show --format=json "$2")
+ test_expect_equal_json "$sexp" "$infix"
+}
+
add_email_corpus
test_begin_subtest "exit code for show invalid query"
notmuch show --entire-thread=true --sort=oldest-first $QUERY > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
+
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+
+ test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
+ test_query_syntax '(or "php" "wizard")' 'php or wizard'
+ test_query_syntax 'wizard' 'wizard'
+ test_query_syntax 'Wizard' 'Wizard'
+ test_query_syntax '(attachment notmuch-help.patch)' 'attachment:notmuch-help.patch'
+
+fi
+
test_done
}
EOF
rm -f ${ovconfig}
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-home
}
EOF
rm -f ${ovconfig}
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-xdg
}
EOF
rm -f ${ovconfig}
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-xdg-profile
}
EOF
#rm -f ${ovconfig}
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-profile
printf("NOT RUN");
}
EOF
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
printf("test.key2 = %s\n", val);
}
EOF
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
unset NOTMUCH_DATABASE
cat <<'EOF' >EXPECTED
== stdout ==
}
EOF
rm -f ${ovconfig}
-NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<'EOF' >EXPECTED
== stdout ==
test.key1 = overridden-home
EOF
test_expect_equal_file EXPECTED OUTPUT
+cat <<EOF > c_head3
+#include <notmuch-test.h>
+int main (int argc, char **argv) {
+ notmuch_status_t stat;
+ notmuch_database_t *db = NULL;
+EOF
+
+cat <<EOF > c_tail3
+ printf("db == NULL: %d\n", db == NULL);
+}
+EOF
+
+test_begin_subtest "open: database set to null on missing config"
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
+ notmuch_status_t st = notmuch_database_open_with_config(argv[1],
+ NOTMUCH_DATABASE_MODE_READ_ONLY,
+ "/nonexistent", NULL, &db, NULL);
+EOF
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "open: database set to null on missing config (env)"
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG="/nonexistent"
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
+ notmuch_status_t st = notmuch_database_open_with_config(argv[1],
+ NOTMUCH_DATABASE_MODE_READ_ONLY,
+ NULL, NULL, &db, NULL);
+EOF
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "create: database set to null on missing config"
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} "/nonexistent"
+ notmuch_status_t st = notmuch_database_create_with_config(argv[1],argv[2], NULL, &db, NULL);
+EOF
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "create: database set to null on missing config (env)"
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG="/nonexistent"
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
+ notmuch_status_t st = notmuch_database_create_with_config(argv[1],
+ NULL, NULL, &db, NULL);
+EOF
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "load_config: database set non-null on missing config"
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR} "/nonexistent"
+ notmuch_status_t st = notmuch_database_load_config(argv[1],argv[2], NULL, &db, NULL);
+EOF
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "load_config: database non-null on missing config (env)"
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+export NOTMUCH_CONFIG="/nonexistent"
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
+ notmuch_status_t st = notmuch_database_load_config(argv[1], NULL, NULL, &db, NULL);
+EOF
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "load_config: database set to NULL on fatal error"
+cat c_head3 - c_tail3 <<'EOF' | test_C
+ notmuch_status_t st = notmuch_database_load_config("relative", NULL, NULL, &db, NULL);
+EOF
+cat <<EOF> EXPECTED
+== stdout ==
+db == NULL: 1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "open: database parameter overrides implicit config"
+notmuch config set database.path ${MAIL_DIR}/nonexistent
+cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
+ const char *path = NULL;
+ notmuch_status_t st = notmuch_database_open_with_config(argv[1],
+ NOTMUCH_DATABASE_MODE_READ_ONLY,
+ NULL, NULL, &db, NULL);
+ printf ("status: %d\n", st);
+ path = notmuch_database_get_path (db);
+ printf ("path: %s\n", path ? path : "(null)");
+EOF
+cat <<EOF> EXPECTED
+== stdout ==
+status: 0
+path: MAIL_DIR
+db == NULL: 0
+== stderr ==
+EOF
+notmuch_dir_sanitize < OUTPUT > OUTPUT.clean
+test_expect_equal_file EXPECTED OUTPUT.clean
+
test_done
add_email_corpus
+
+if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
+
+ count=$(notmuch count --lastmod '*' | cut -f 3)
+ for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
+ '(or (and) (or) (not (and)))'; do
+ test_begin_subtest "reindex all messages: $query"
+ notmuch reindex --query=sexp "$query"
+ output=$(notmuch count --lastmod '*' | cut -f 3)
+ count=$((count + 1))
+ test_expect_equal "$output" "$count"
+ done
+
+fi
+
notmuch tag +usertag1 '*'
notmuch search '*' 2>1 | notmuch_search_sanitize > initial-threads
notmuch tag -attachment2 -encrypted2 -signed2 '*'
test_expect_equal_file EXPECTED OUTPUT
+backup_database
test_begin_subtest 'reindex moves a message between threads'
notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED
# re-parent
notmuch reindex id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net
notmuch search --output=threads id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest 'reindex detects removal of all files'
+notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
+# remove both copies
+mv $MAIL_DIR/cur/51:2,* duplicate-message-2.eml
+notmuch reindex id:20091117232137.GA7669@griffis1.net
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+backup_database
test_begin_subtest 'reindex detects removal of all files'
notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
# remove both copies
notmuch reindex id:20091117232137.GA7669@griffis1.net
notmuch search --output=messages '*' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
+restore_database
test_begin_subtest "reindex preserves properties"
cat <<EOF > prop-dump
--- /dev/null
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="===============9060418334135509864=="
+MIME-Version: 1.0
+From: Notmuch test suite <test_suite@notmuchmail.org>
+To: test_suite@notmuchmail.org
+Subject: testing encrypted rfc822 attachments
+Date: Sat, 03 Jul 2021 16:00:02 -0300
+Message-ID: <encrypted-rfc822-attachment@crypto.notmuchmail.org>
+User-Agent: alot/0.9.1
+
+--===============9060418334135509864==
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Type: application/pgp-encrypted; charset="us-ascii"
+
+Version: 1
+--===============9060418334135509864==
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Type: application/octet-stream; charset="us-ascii"
+
+-----BEGIN PGP MESSAGE-----
+
+hIwDxE023q1UqxYBBACGKSDv5/rcwScSf9n33cZZPPxltQgxkDaClMGY2DARgebE
+9rpE2O/4eoaEbdu+2shahPLIbD0wuRiGVpMIIloNNucl3f15h1wXPZbTwK7sNxJq
+HycSf8sT1fkolmC9s9X0r5xHgk0G4klAqr5C3GOk7Y6wsHTYGqzDvBFEB0LvaoUC
+DANw48DehwaEUQEP/iaiYeMDUsOzFZodKZOlQWese2JPTsRwF7KrTl8C93MrZqAh
+A1pQjUH9cafj8mwDXA9ZCsYZs7r84IxShI+dUhinBSCq8F61OlLP859wq+wpKU7n
+PNVA5bfd//4hRFvDT33ZlgeeeCcRo7h4IDjJwFDYsf0Ysqvo+IKipVNXXlAcGYYI
+DVBucB0fYaVHWRKxw50mo02zKP2/GW6K3p1nxGTf73cKjc9of+VOvvMByODaJ+ne
+WIuzZMqz0vfQ0UvRVBjlsXtB7VpCqJZWkubqqwwP6+2WOCA/c5LC3z2f/BK90EQh
+/JrKfDR083UNhu4SjNwL/TF4ET33JHf1u5Gmzqx+eO00pfhVvkyz6LYImkE8ky/+
+bXyJY8iDq7dxtfqhzZbeNe4fafU/avXxTA5UkWTnYhCqyd2bvAYH3Ep3L7lSv6SQ
+Tsy0jjTsWJoSq6jRIzJuo2mX2MBKPBfLZs4tH71/4RppECletNnS4ZlxiV4LNrWE
+LrXQvE1V+mJ82muucIe7w52nf3UWjQqTA73+Ml0aK+lIhbckRIovAw1sGzRrbTEX
+xLCgz7BYDMhs5mgtfiMAzGox4xGxi56Ge519vdbddan4G92mPlLl1IPOXkO8GyLO
+D4IiPp5ilPy6uThuxxIFemxxUREbPrfLJNA8W7aRPrHz4YcgZhrAV9I4C+xE0ukB
+i8MoJeFvbCGPyTwVDn8XfFKynlZYm1f8NIVMSj5JfV8J3Om9jzDp6hx+52iUQEbW
+C9g4kfPZY8h0RMggdOlZsaR8j26xuW+fEtz7ucJIqfJ/ElTH+4bm8MK2qPZniRWv
+ej2md4bP4Bo5DXydzxz7O7TBL6/Jsp7pJhHUUb36OnTWvInyg71LPT1QIxdRvXr9
+vNhrEBDX3MNf7RyXczvBcc+cLRo+zV+T4b8wd2kwXskWgKrGUJEe2TItdsafaQ9B
+BkuVGu6cIDa6STyCJiOB68KIXiDuADSWJiiR+gZr4eU6vzfhR27LMQt/g+oPW+U4
+1AvzUl9uXjTMC2vFuTQ4M2g0WmksCNpPpzOu/QlBmRqpP2Fg9UuLv6ITWeCxp439
+g5NT5KXE2IiruL/DS0KEpWVNe4ayGzRvMawFuU582xbIzXjvilUZrW+p6req+oeF
+QWTWHGDojTvULQPV2c91lWnLaNXVethfF4hrM5MIL+EwVs3sUXFMr1kX7VNrK0QH
+Uos7nc4G9xngpdvwF4ImldD83O8qxOVzIfk5Dhz+etTH4HbnnDvmQ/FIYvjzGviR
+ZeVwdCjv/9TC4yY3nJFKMwGp70jVa1vbmWL68HVNRyAVwnQnu2UlI8UR43iVZyUH
+ZY0Nr0rbse/pvZyX4//EVOFLUR7K4GG0N4Kz41q5ZB8rI4Fl6QJhgIFJds13iM77
+n+wqLQE7kllgI32E4U9B
+=YlXg
+-----END PGP MESSAGE-----
+
+--===============9060418334135509864==--
--- /dev/null
+ 2010-12-29 François Boulogne ─►[aur-general] Guidelines: cp, mkdir vs install ( ui)
+ 2010-12-16 Olivier Berger ─►Essai accentué ( ui)
+ 2009-11-18 Chris Wilson ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once ( ui)
+ 2009-11-18 Alex Botero-Lowry ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (& ui)
+ 2009-11-18 Carl Worth ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop ( ui)
+ 2009-11-17 Ingmar Vanhassel ┬►[notmuch] [PATCH] Typsos ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Adrian Perez de Cast ┬►[notmuch] Introducing myself ( =ui)
+ 2009-11-18 Keith Packard ├─► ... ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Israel Herraiz ┬►[notmuch] New to the list ( ui)
+ 2009-11-18 Keith Packard ├─► ... ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Jan Janak ┬►[notmuch] What a great idea! ( ui)
+ 2009-11-17 Jan Janak ├─► ... ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Jan Janak ┬►[notmuch] [PATCH] Older versions of install do not support -C. ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Aron Griffis ┬►[notmuch] archive ( ui)
+ 2009-11-18 Keith Packard ╰┬► ... ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Keith Packard ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags ( ui)
+ 2009-11-18 Carl Worth ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags ( ui)
+ 2009-11-17 Lars Kellogg-Stedman ┬►[notmuch] Working with Maildir storage? ( = i)
+ 2009-11-17 Mikhail Gusarov ├┬► ... ( =ui)
+ 2009-11-17 Lars Kellogg-Stedman │╰┬► ... ( =ui)
+ 2009-11-17 Mikhail Gusarov │ ├─► ... ( ui)
+ 2009-11-17 Keith Packard │ ╰┬► ... ( ui)
+ 2009-11-18 Lars Kellogg-Stedman │ ╰─► ... ( =ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-17 Mikhail Gusarov ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers ( i)
+ 2009-11-17 Mikhail Gusarov ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4 ( ui)
+ 2009-11-17 Carl Worth ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers ( ui)
+ 2009-11-17 Keith Packard ╰┬► ... ( ui)
+ 2009-11-18 Carl Worth ╰─► ... ( ui)
+ 2009-11-18 Keith Packard ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap ( ui)
+ 2009-11-18 Alexander Botero-Low ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap ( ui)
+ 2009-11-18 Alexander Botero-Low ─►[notmuch] request for pull ( ui)
+ 2009-11-18 Jjgod Jiang ┬►[notmuch] Mac OS X/Darwin compatibility issues ( ui)
+ 2009-11-18 Alexander Botero-Low ╰┬► ... ( ui)
+ 2009-11-18 Jjgod Jiang ╰┬► ... ( ui)
+ 2009-11-18 Alexander Botero-Low ╰─► ... ( ui)
+ 2009-11-18 Rolland Santimano ─►[notmuch] Link to mailing list archives ? ( ui)
+ 2009-11-18 Jan Janak ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags ( ui)
+ 2009-11-18 Stewart Smith ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting ( ui)
+ 2009-11-18 Stewart Smith ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order ( ui)
+ 2009-11-18 Stewart Smith ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. ( ui)
+ 2009-11-18 Lars Kellogg-Stedman ┬►[notmuch] "notmuch help" outputs to stderr? (&=ui)
+ 2009-11-18 Lars Kellogg-Stedman ╰─► ... (&=ui)
+ 2009-11-17 Mikhail Gusarov ─►[notmuch] [PATCH] Handle rename of message file ( ui)
+ 2009-11-17 Alex Botero-Lowry ┬►[notmuch] preliminary FreeBSD support (& ui)
+ 2009-11-17 Carl Worth ╰─► ... ( ui)
+End of search results.
--- /dev/null
+ 2010-12-29 François Boulogne [aur-general] Guidelines: cp, mkdir vs install ( ui)
+ 2010-12-16 Olivier Berger Essai accentué ( ui)
+ 2009-11-18 Chris Wilson [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once( ui)
+ 2009-11-18 Carl Worth [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop( ui)
+ 2009-11-18 Carl Worth [notmuch] [PATCH] Typsos ( ui)
+ 2009-11-18 Carl Worth [notmuch] Introducing myself ( ui)
+ 2009-11-18 Carl Worth [notmuch] New to the list ( ui)
+ 2009-11-18 Carl Worth [notmuch] What a great idea! ( ui)
+ 2009-11-18 Carl Worth [notmuch] [PATCH] Older versions of install do not support -C.( ui)
+ 2009-11-18 Carl Worth [notmuch] archive ( ui)
+ 2009-11-18 Carl Worth [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags( ui)
+ 2009-11-18 Carl Worth [notmuch] Working with Maildir storage? ( ui)
+ 2009-11-18 Carl Worth [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
+ 2009-11-18 Alexander Botero-Low[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap( ui)
+ 2009-11-18 Keith Packard [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap( ui)
+ 2009-11-18 Alexander Botero-Low[notmuch] request for pull ( ui)
+ 2009-11-18 Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues ( ui)
+ 2009-11-18 Jjgod Jiang [notmuch] Mac OS X/Darwin compatibility issues ( ui)
+ 2009-11-18 Alexander Botero-Low[notmuch] Mac OS X/Darwin compatibility issues ( ui)
+ 2009-11-18 Rolland Santimano [notmuch] Link to mailing list archives ? ( ui)
+ 2009-11-18 Jan Janak [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags( ui)
+ 2009-11-18 Jjgod Jiang [notmuch] Mac OS X/Darwin compatibility issues ( ui)
+ 2009-11-18 Stewart Smith [notmuch] [PATCH] count_files: sort directory in inode order before statting( ui)
+ 2009-11-18 Keith Packard [notmuch] archive ( ui)
+ 2009-11-18 Keith Packard [notmuch] Introducing myself ( ui)
+ 2009-11-18 Keith Packard [notmuch] New to the list ( ui)
+ 2009-11-18 Stewart Smith [notmuch] [PATCH 2/2] Read mail directory in inode number order( ui)
+ 2009-11-18 Stewart Smith [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs.( ui)
+ 2009-11-18 Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr? (&=ui)
+ 2009-11-18 Lars Kellogg-Stedman[notmuch] "notmuch help" outputs to stderr? (&=ui)
+ 2009-11-18 Lars Kellogg-Stedman[notmuch] Working with Maildir storage? ( =ui)
+ 2009-11-18 Alex Botero-Lowry [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop(& ui)
+ 2009-11-17 Ingmar Vanhassel [notmuch] [PATCH] Typsos ( ui)
+ 2009-11-17 Aron Griffis [notmuch] archive ( ui)
+ 2009-11-17 Adrian Perez de Cast[notmuch] Introducing myself ( =ui)
+ 2009-11-17 Israel Herraiz [notmuch] New to the list ( ui)
+ 2009-11-17 Jan Janak [notmuch] What a great idea! ( ui)
+ 2009-11-17 Jan Janak [notmuch] What a great idea! ( ui)
+ 2009-11-17 Jan Janak [notmuch] [PATCH] Older versions of install do not support -C.( ui)
+ 2009-11-17 Keith Packard [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags( ui)
+ 2009-11-17 Keith Packard [notmuch] Working with Maildir storage? ( ui)
+ 2009-11-17 Keith Packard [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
+ 2009-11-17 Mikhail Gusarov [notmuch] [PATCH] Handle rename of message file ( ui)
+ 2009-11-17 Mikhail Gusarov [notmuch] Working with Maildir storage? ( ui)
+ 2009-11-17 Lars Kellogg-Stedman[notmuch] Working with Maildir storage? ( =ui)
+ 2009-11-17 Carl Worth [notmuch] preliminary FreeBSD support ( ui)
+ 2009-11-17 Alex Botero-Lowry [notmuch] preliminary FreeBSD support (& ui)
+ 2009-11-17 Mikhail Gusarov [notmuch] Working with Maildir storage? ( =ui)
+ 2009-11-17 Lars Kellogg-Stedman[notmuch] Working with Maildir storage? ( =ui)
+ 2009-11-17 Carl Worth [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
+ 2009-11-17 Mikhail Gusarov [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++ file with gcc 4.4( ui)
+ 2009-11-17 Mikhail Gusarov [notmuch] [PATCH 1/2] Close message file after parsing message headers( ui)
+End of search results.
--- /dev/null
+ ui 2010-12-29 [1/1] François Boulogne [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+ ui 2010-12-16 [1/1] Olivier Berger Essai accentué (inbox unread)
+ ui 2009-11-18 [1/1] Chris Wilson [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+& ui 2009-11-18 [2/2] Alex Botero-Lowry, Carl Worth [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+ ui 2009-11-18 [2/2] Ingmar Vanhassel, Carl Worth [notmuch] [PATCH] Typsos (inbox unread)
+ =ui 2009-11-18 [3/3] Adrian Perez de Castro, Keith Packard, Carl Worth [notmuch] Introducing myself (inbox signed unread)
+ ui 2009-11-18 [3/3] Israel Herraiz, Keith Packard, Carl Worth [notmuch] New to the list (inbox unread)
+ ui 2009-11-18 [3/3] Jan Janak, Carl Worth [notmuch] What a great idea! (inbox unread)
+ ui 2009-11-18 [2/2] Jan Janak, Carl Worth [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+ ui 2009-11-18 [3/3] Aron Griffis, Keith Packard, Carl Worth [notmuch] archive (inbox unread)
+ ui 2009-11-18 [2/2] Keith Packard, Carl Worth [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+ =ui 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth [notmuch] Working with Maildir storage? (inbox signed unread)
+ ui 2009-11-18 [5/5] Mikhail Gusarov, Carl Worth, Keith Packard [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+ ui 2009-11-18 [2/2] Keith Packard, Alexander Botero-Lowry [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+ ui 2009-11-18 [1/1] Alexander Botero-Lowry [notmuch] request for pull (inbox unread)
+ ui 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+ ui 2009-11-18 [1/1] Rolland Santimano [notmuch] Link to mailing list archives ? (inbox unread)
+ ui 2009-11-18 [1/1] Jan Janak [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+ ui 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
+ ui 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+ ui 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
+&=ui 2009-11-18 [2/2] Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+ ui 2009-11-17 [1/1] Mikhail Gusarov [notmuch] [PATCH] Handle rename of message file (inbox unread)
+& ui 2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth [notmuch] preliminary FreeBSD support (attachment inbox unread)
+End of search results.
const char *notmuch_requested_db_uuid = NULL;
void
-notmuch_process_shared_options (unused (const char *dummy))
+notmuch_process_shared_options (unused (notmuch_database_t *notmuch),
+ unused (const char *dummy))
{
}
--- /dev/null
+Issuer ...: /CN=Notmuch Test Suite
+Serial ...: 6F748C94BD0C67A9
+Subject ..: /CN=Notmuch Test Suite
+ aka ..: test_suite@notmuchmail.org
+Keygrip ..: 1727B9C7108D50333614F3B1DD0807F624B31130
+
+-----BEGIN PKCS12-----
+MIIJ+AIBAzCCCb4GCSqGSIb3DQEHAaCCCa8EggmrMIIJpzCCBAcGCSqGSIb3DQEH
+BqCCA/gwggP0AgEAMIID7QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIcfMY
+MS7tOpcCAggAgIIDwFu7ZRNrXCb0eKei44aeBZPRs9YI/5EpMcFuc8j4/8T1HkIt
+GuRe/HzRmoiLZcAMOzGC/hF8TkHlNeUZ7rOSpCg4UlBVWJS6avTMHHsakDvTV/7q
+X5VNi4pLUuyEToGTAPHV+s5P/gYYG6mFPkwG/pDDlAcgMhgtuPY/lQp6IS/E6CaR
+fhcnQiPq9ySTqO7UNwIyMwtAtSHkgBaje8UbOkQch4lg51i97rm9m4EMvklKtjXc
+Ud4aTEuoZguPmdBdLvF5QxqJf6Bm9lHa1Awhru2gBWQf9TjX8bwK9Xsv8G6gPOwc
+LVpIR9fMZtgBbc+heeJTjfn6VqEy881ckbkz+38hiN3pbLMuATM7QAY3u3N4whM6
+Hmfyl3iqba84Pl93zaUzqazAUeFdqcqSpAUGkS4gU6klr9qi3NicaGbry1DySYU7
+2h4xy3j7eiHxqdWaibdPoBC8CEbPaFj2qnOVsZykxG6zPvbEB+5sJ/a+T6xm1Btx
+N6vXR7ObbXlpC4pRkS32ehuRbY6wc6H2KKepOMCu7x10tN0Up5ccNxvkT26QIrEE
+LW296ijCLbsRhWymDtopWAZHcXXIu0fJ4tocSp2c3lojSEYu1jlMXR+Pa4R8EtgZ
+lb5+NqISxjUlMMWzGDyhrp9ImcsZmpv6N8zPcZVyU+M1/h+p9ur/IOVZU9P1vIKy
+kcM4pslr0JhLfnZCLZ+3Ux1yKAcndGZFPb1vZ83jyZKR38BVSGu53ODaBJBqSMHu
+Mv2Na/qzvQBSVJuWF9cAhiVd7v9R/EvT0zmljN4w7l4EXsB5wRsO1wvlL+MhwaET
+dIHbRH2GD3gERX6oTc3t3cgritVePk70rCxQDxn5zUbjW7dNIlIobAumLHBfgSxR
+QCE6gxdTm5MW2O9hnfTSQvliVaGU1gd0M3BRiqeNpPPxnloGKnOEODM381F4HxyR
+CzO2r/2aKJP+U5HxSf4cljp3/Lripxykzfqc9/xZshl+jGixsSSm+Ul916Hpj2Rt
+j9vHg4H9YfJTGdvzxZcvZCvNSy3ygtjx0++SrI5hGHKjpVJIK2/9Wi39q5s6LkiA
+RCjvuoBBcQXm++69X7QGWSsGFtwerCGnq3nAxGpHVKVGTvFYMAg6y1RR0zvE0SuM
+MZegD8w45QyrmiPqSRM7/RtqVdA+r/wiJwWerUBq+mrCvJHB2NRcjiUiCJY1bjRU
+ATMfB0uZaNInUXiLDGxp2mdBgdFVq7sYTbq+OvprzxeAjIvodxl3J9ThvJnt1fzK
+RPCJw5COI60ibE3XTTCCBZgGCSqGSIb3DQEHAaCCBYkEggWFMIIFgTCCBX0GCyqG
+SIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAiEe8CcxIIv9wICCAAE
+ggTIujut93lYPUsKc/JNhZhUWS/RHHog6d8ZAjpFvXpyD8Z2z4A4PpgIn8eUSRW5
+Gwp8izR+16Tj3ht52pJ5Y1x27/S3l3sDlekEZ/33X/AdLFWAXbcibmwtRea1ucKZ
+ze3DJM7CvuRvVSBG8XubPGi3pZkEjHBGQqgtsTnxlBp0PXl7wxfyT7F6gOH2DGYP
+bYzNa2fnY8twEcUYhuksI/eh9Zwj9TrF0HWq1hwp0tDCfqutzshSX2GQ/p0raL3B
+C2stHBjl0OVUfDHpqQ5OJWbQvGcJntECqu4gmSJohunObaUKcN8xs+FzB5czpmsT
+W/pyR58nc8QhTttByqZN3EerhEogWDZj4tQ6dK8p6bqLO/0qqBehZGchfof5Evwj
+VFsvVGD8xVLQWWAFnrQs5+U56NQEbmZzN5RCI7FEK2VVOeG03dpXyoAQyxuYrsYU
+3znmoSleIqDDBFD21YePUcJZ0R8AQsvgV11tdwPWqr1hk0bIazLQ9rappGrTgkK8
+DFdQKSH1dRvjqtbuDyY7j5PXXJTXthVv9T9N7Vp6qU+pWBQ1Mz30J+fHX2ilEnbi
+tQ49hwt1+/2Zkmwz3reoEnxYOKzCg/ySIpQ27/Hx4xZ+ecEzX/0IxCkHeAV3V3bB
+1z8wFxWEh1s9hL6C8lRk/wQ9KsKaxM7BdLw7RjiqEwR4HgeCqMPdCVQQpILARDC8
+Poz8xUmjv7HyIvvyBUP12YdIj74Jjj0Mm2r/FDj7nsXxkjXMZEMMKK3oVaAMq8Bd
+cO4VQXDd7bgNzLF9PKxWNjoCuQcPJXwMPqlFoc06BLPstEaR4enafv0Pd4l0pyME
+YgezyVW+3yFEsbbB2UUs0r7oqxsDFU9/iHf8O3nu3NuKTJkux4uMlOTBKsm6sY7k
+GduP2UA+WU27jHrf4zQQbkDLG1lJFfcaKzlcOmz5B9iZwugBz9Y28w5f2/12Kqrh
+4tibFBUG0E85KAb1wnFUNUx06OMX229U1M0E1LHbcUJ9mcRipONPVn0FRi8XzaLK
+023XRoihuoWhVUiB1OJ2eZW1JnUYRztfa3nfmGjXv4VGkxYlnTkE9z0PAAhf6t5A
+7Ir0y1JUeOlBITTcojOp6qQ8tMQQ5wRk1oncHiw3WwJvFN6fOa9Q/+4ZmULHz0vV
+Xl+Qio8B7/4jqZoT4e/gK6U/zHriznLzqp63LjP47eFRXTfuXslaCt7YF75Mq2J6
+VPA+qfYRw0K5BvDUkr8c+nLP2AiDaEYVBHGdBRTlWO9UkcB1F4cuZZiU5MZbxVrb
+Db+zGWW6AT+4XTO4z9KmAqgTTv1+BQrLxNI+RG8JfQapUKQyB794F4kXK2yhd1P3
+XS9cwh24COiqbOpI1nB5qn7cn4RRHW156LWGF+VJFdxR6Wu3vZx/kZGevG9o1ARF
+z1l9mbGyhwnUJO1EQwjbppvRou1bZuNbuRgLmHKEVPAv+J+7hLXZAnRdwoV0x91t
+bpmy4qyxA/90DHguIhRVcKsYBrdShY7LXdZArECBhMY9R41D6v1yyhC6fL6PKR5g
+DaluN2K9TBALzZH7NnNdE14l+56+kLc9Fq8JXsq3rxdeBTsNl09fHPf9w5VLkq4I
+doNcPPlta0Q0xJNa/RYENCJpAMZdMFIJ558uMXwwVQYJKoZIhvcNAQkUMUgeRgBH
+AG4AdQBQAEcAIABlAHgAcABvAHIAdABlAGQAIABjAGUAcgB0AGkAZgBpAGMAYQB0
+AGUAIABlADAAOQA3ADIAYQA0ADcwIwYJKoZIhvcNAQkVMRYEFGFvRs1zg0xjhHdW
+rw37ZKbglypHMDEwITAJBgUrDgMCGgUABBSluQBa+tVpYVYmB/zAZuPE9NnargQI
+XWSQTDEONWgCAggA
+-----END PKCS12-----
(lambda (x) `(prog1 ,x (notmuch-post-command)))
body)))
+;; For testing functions in
+;; notmuch-{search,tree,unsorted}-result-format
+(defun notmuch-test-result-flags (format-string result)
+ (let ((tags-to-letters (quote (("attachment" . "&")
+ ("signed" . "=")
+ ("unread" . "u")
+ ("inbox" . "i"))))
+ (tags (plist-get result :tags)))
+ (format format-string
+ (mapconcat (lambda (t2l)
+ (if (member (car t2l) tags)
+ (cdr t2l)
+ " "))
+ tags-to-letters ""))))
+
+;; Log any signalled error (and other messages) to MESSAGES
+;; Log "COMPLETE" if forms complete without error.
+(defmacro test-log-error (&rest body)
+ `(progn
+ (with-current-buffer "*Messages*"
+ (let ((inhibit-read-only t)) (erase-buffer)))
+ (condition-case err
+ (progn ,@body
+ (message "COMPLETE"))
+ (t (message "%s" err)))
+ (with-current-buffer "*Messages*" (test-output "MESSAGES"))))
+
;; For historical reasons, we hide deleted tags by default in the test
;; suite
(setq notmuch-tag-deleted-formats
_gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
at_exit_function _gnupg_exit
mkdir -p -m 0700 "$GNUPGHOME"
- openssl pkcs12 -export -passout pass: -inkey "$NOTMUCH_SRCDIR/test/smime/key+cert.pem" \
- < "$NOTMUCH_SRCDIR/test/smime/test.crt" | \
- gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \
- --disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<''
+ gpgsm --batch --no-tty --no-common-certs-import --pinentry-mode=loopback --passphrase-fd 3 \
+ --disable-dirmngr --import >"$GNUPGHOME"/import.log 2>&1 3<<<'' <$NOTMUCH_SRCDIR/test/smime/0xE0972A47.p12
fpr=$(gpgsm --batch --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
echo "$fpr S relax" >> "$GNUPGHOME/trustlist.txt"
gpgsm --quiet --batch --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/ca.crt
test_python () {
# Note: if there is need to print debug information from python program,
# use stdout = os.fdopen(6, 'w') or stderr = os.fdopen(7, 'w')
- PYTHONPATH="$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
+ PYTHONPATH="$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage:$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
$NOTMUCH_PYTHON -B - > OUTPUT
}
#include <stdbool.h>
#include <gmodule.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* The utf8 encoded string would tokenize as a single word, according
* to xapian. */
bool unicode_word_utf8 (const char *str);
typedef gunichar notmuch_unichar;
+#ifdef __cplusplus
+}
+#endif
#endif