mirror of
https://git.notmuchmail.org/git/notmuch
synced 2025-03-14 03:25:15 +01:00
notmuch release 0.36-1 for unstable (sid) [dgit]
[dgit distro=debian no-split --quilt=linear] -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEkiyHYXwaY0SiY6fqA0U5G1WqFSEFAmJmjoQACgkQA0U5G1Wq FSEiTw//VGUFqchEOeSt0CeQTuk48CLf8ibol6NfQ9w/HUIH2O0nmG2Xj9VjtmbD Y97SytUE2lTKTaB7D7/k8NrGHBFCB0njIK4uxklzWjERS4UeG76Iavxe0gtiOTL+ /SjbOwn/Vw3NDZPwM6RMwV7oGl+IoyATCCEaUTKnD8OQiZsnztI0LuJtdEgHYrTl JMqbY+j1Vt8zFe2aUPNcEgTH4W9xT4k9ByWu0AOCXk5bt7WrPMB/rqm4pWr56r5T 7vbUMClW9bgB+Dudp9c7ZHt90gg6QM+vPp+JQABg/pFOTwPX71JW4bAncn8ZNkl7 6PAV1G47iT35UfPnax3cyBSTJTgE9O9KCLPelBg1BHauvqf8c6m0DnfoMsXb/bqc AuHZ8PlpgOR2iiW1FOWsYk+8abCz3FU3iDnVJfoRa4wkbOKM5fbDcn3ei7yeyMH2 V5VSaki8lWQXcgcIjHCFw+DlpdG6GUCOl7LdowfKAMXr15Zml2LyHcxOdxccrEAO CCGG8Gr+Ms6Lzp2SSc+9eKxwpx6GAUGhSagUE9VYP9+E9zNX6rh9UGa9ap+MJBmv 50vCBFbp3eQRqfOb8ip7TTe9+YFJkFl/CtPWbNHv6j+nUDTa0givfhWxJ7Q/IGv7 77d3DKEZ+B36oIQGnjPZ924V7B+zoVmlU0BQspyVNS918IMHxMU= =+lFq -----END PGP SIGNATURE----- Merge tag 'debian/0.36-1' into debian/bullseye-backports notmuch release 0.36-1 for unstable (sid) [dgit] [dgit distro=debian no-split --quilt=linear]
This commit is contained in:
commit
97b6a43d46
61 changed files with 1594 additions and 444 deletions
|
@ -73,7 +73,7 @@ release: verify-source-tree-and-version
|
|||
ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
|
||||
pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
|
||||
mkdir -p releases
|
||||
mv $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
|
||||
mv $(TAR_FILE) $(DEB_TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
|
||||
$(MAKE) VERSION=$(VERSION) release-message > $(PACKAGE)-$(VERSION).announce
|
||||
ifeq ($(REALLY_UPLOAD),yes)
|
||||
git push origin $(VERSION) release pristine-tar
|
||||
|
@ -92,7 +92,11 @@ pre-release:
|
|||
ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
|
||||
pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
|
||||
mkdir -p releases
|
||||
mv $(TAR_FILE) $(DEB_TAR_FILE) releases
|
||||
mv $(TAR_FILE) $(DEB_TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) releases
|
||||
ifeq ($(REALLY_UPLOAD),yes)
|
||||
git push origin $(UPSTREAM_TAG) release pristine-tar
|
||||
cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(DETACHED_SIG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
|
||||
endif
|
||||
|
||||
.PHONY: debian-snapshot
|
||||
debian-snapshot:
|
||||
|
@ -120,8 +124,7 @@ release-message:
|
|||
@echo "Which can be verified with:"
|
||||
@echo ""
|
||||
@echo " $(RELEASE_URL)/$(SHA256_FILE)"
|
||||
@echo -n " "
|
||||
@cat releases/$(SHA256_FILE)
|
||||
@sed "s/^/ /" releases/$(SHA256_FILE)
|
||||
@echo ""
|
||||
@echo " $(RELEASE_URL)/$(DETACHED_SIG_FILE)"
|
||||
@echo " (signed by `getent passwd "$$USER" | cut -d: -f 5 | cut -d, -f 1`)"
|
||||
|
@ -169,7 +172,7 @@ release-checks:
|
|||
|
||||
.PHONY: verify-newer
|
||||
verify-newer:
|
||||
@echo -n "Checking that no $(VERSION) release already exists..."
|
||||
@printf %s "Checking that no $(VERSION) release already exists..."
|
||||
@wget -q --no-check-certificate -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
|
||||
case $$? in \
|
||||
8) echo "Good." ;; \
|
||||
|
|
57
NEWS
57
NEWS
|
@ -1,3 +1,60 @@
|
|||
Notmuch 0.36 (2022-04-25)
|
||||
=========================
|
||||
|
||||
Library
|
||||
-------
|
||||
|
||||
Add the `sexp` prefix to the infix (traditional) query parser. This
|
||||
allows specific subqueries to be parsed by the sexp parser (with
|
||||
appropropriate quoting). See `notmuch-search-terms(7)` for details.
|
||||
|
||||
Add another heuristic to regexp fields to prevent phrase parsing of
|
||||
bracketed sub-expressions.
|
||||
|
||||
Command Line Interface
|
||||
----------------------
|
||||
|
||||
Envelope from ("From ") headers are now escaped as X-Envelope-From: in
|
||||
input to `notmuch-insert`. This prevents creating mbox files when
|
||||
calling `notmuch-insert` from e.g. `postfix`.
|
||||
|
||||
Python (CFFI) Bindings
|
||||
----------------------
|
||||
|
||||
Use the `config_pairs` API in ConfigIterator. This returns all
|
||||
matching key-value pairs, not just those that happen to be stored in
|
||||
the database.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Reorganize documention for `notmuch-config`. Add a few links from
|
||||
other man pages.
|
||||
|
||||
Emacs
|
||||
-----
|
||||
|
||||
Bind the usual undo key sequences to new command
|
||||
"notmuch-tag-undo". This allows transparent undo of tagging
|
||||
operations.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
Fix smime.4 with newer gmime. Unset `XDG_DATA_HOME` and `MAILDIR` for tests.
|
||||
|
||||
New add-on tool: notmuch-web
|
||||
-----------------------------
|
||||
|
||||
The new devel/ tool `notmuch-web` is a very thin web client. It
|
||||
supports a full search interface for one user: there is no facility
|
||||
for multiple users provided today. See the notmuch-web README file
|
||||
for more information.
|
||||
|
||||
Be careful about running it on a network-connected system: it will
|
||||
expose a web interface that requires no authentication but exposes
|
||||
your mail store.
|
||||
|
||||
Notmuch 0.35 (2022-02-06)
|
||||
=========================
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ ffibuilder.cdef(
|
|||
typedef struct _notmuch_string_map_iterator notmuch_message_properties_t;
|
||||
typedef struct _notmuch_directory notmuch_directory_t;
|
||||
typedef struct _notmuch_filenames notmuch_filenames_t;
|
||||
typedef struct _notmuch_config_list notmuch_config_list_t;
|
||||
typedef struct _notmuch_config_pairs notmuch_config_pairs_t;
|
||||
typedef struct _notmuch_indexopts notmuch_indexopts_t;
|
||||
|
||||
const char *
|
||||
|
@ -325,18 +325,18 @@ ffibuilder.cdef(
|
|||
notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value);
|
||||
notmuch_status_t
|
||||
notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value);
|
||||
notmuch_status_t
|
||||
notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out);
|
||||
notmuch_config_pairs_t *
|
||||
notmuch_config_get_pairs (notmuch_database_t *db, const char *prefix);
|
||||
notmuch_bool_t
|
||||
notmuch_config_list_valid (notmuch_config_list_t *config_list);
|
||||
notmuch_config_pairs_valid (notmuch_config_pairs_t *config_list);
|
||||
const char *
|
||||
notmuch_config_list_key (notmuch_config_list_t *config_list);
|
||||
notmuch_config_pairs_key (notmuch_config_pairs_t *config_list);
|
||||
const char *
|
||||
notmuch_config_list_value (notmuch_config_list_t *config_list);
|
||||
notmuch_config_pairs_value (notmuch_config_pairs_t *config_list);
|
||||
void
|
||||
notmuch_config_list_move_to_next (notmuch_config_list_t *config_list);
|
||||
notmuch_config_pairs_move_to_next (notmuch_config_pairs_t *config_list);
|
||||
void
|
||||
notmuch_config_list_destroy (notmuch_config_list_t *config_list);
|
||||
notmuch_config_pairs_destroy (notmuch_config_pairs_t *config_list);
|
||||
"""
|
||||
)
|
||||
|
||||
|
|
|
@ -13,27 +13,42 @@ class ConfigIter(base.NotmuchIter):
|
|||
def __init__(self, parent, iter_p):
|
||||
super().__init__(
|
||||
parent, iter_p,
|
||||
fn_destroy=capi.lib.notmuch_config_list_destroy,
|
||||
fn_valid=capi.lib.notmuch_config_list_valid,
|
||||
fn_get=capi.lib.notmuch_config_list_key,
|
||||
fn_next=capi.lib.notmuch_config_list_move_to_next)
|
||||
fn_destroy=capi.lib.notmuch_config_pairs_destroy,
|
||||
fn_valid=capi.lib.notmuch_config_pairs_valid,
|
||||
fn_get=capi.lib.notmuch_config_pairs_key,
|
||||
fn_next=capi.lib.notmuch_config_pairs_move_to_next)
|
||||
|
||||
def __next__(self):
|
||||
item = super().__next__()
|
||||
return base.BinString.from_cffi(item)
|
||||
|
||||
# skip pairs whose value is NULL
|
||||
while capi.lib.notmuch_config_pairs_valid(super()._iter_p):
|
||||
val_p = capi.lib.notmuch_config_pairs_value(super()._iter_p)
|
||||
key_p = capi.lib.notmuch_config_pairs_key(super()._iter_p)
|
||||
if key_p == capi.ffi.NULL:
|
||||
# this should never happen
|
||||
raise errors.NullPointerError
|
||||
key = base.BinString.from_cffi(key_p)
|
||||
capi.lib.notmuch_config_pairs_move_to_next(super()._iter_p)
|
||||
if val_p != capi.ffi.NULL and base.BinString.from_cffi(val_p) != "":
|
||||
return key
|
||||
self._destroy()
|
||||
raise StopIteration
|
||||
|
||||
class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
|
||||
"""The config key/value pairs stored in the database.
|
||||
"""The config key/value pairs loaded from the database, config file,
|
||||
and and/or defaults.
|
||||
|
||||
The entries are exposed as a :class:`collections.abc.MutableMapping` object.
|
||||
Note that setting a value to an empty string is the same as deleting it.
|
||||
|
||||
Mutating (deleting or updating values) in the map persists only in
|
||||
the database, which can be shadowed by config files.
|
||||
|
||||
:param parent: the parent object
|
||||
:param ptr_name: the name of the attribute on the parent which will
|
||||
return the memory pointer. This allows this object to
|
||||
access the pointer via the parent's descriptor and thus
|
||||
trigger :class:`MemoryPointer`'s memory safety.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, parent, ptr_name):
|
||||
|
@ -77,11 +92,10 @@ class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
|
|||
|
||||
:raises NullPointerError: If the iterator can not be created.
|
||||
"""
|
||||
configlist_pp = capi.ffi.new('notmuch_config_list_t**')
|
||||
ret = capi.lib.notmuch_database_get_config_list(self._ptr(), b'', configlist_pp)
|
||||
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
|
||||
raise errors.NotmuchError(ret)
|
||||
return ConfigIter(self._parent, configlist_pp[0])
|
||||
config_pairs_p = capi.lib.notmuch_config_get_pairs(self._ptr(), b'')
|
||||
if config_pairs_p == capi.ffi.NULL:
|
||||
raise KeyError
|
||||
return ConfigIter(self._parent, config_pairs_p)
|
||||
|
||||
def __len__(self):
|
||||
return sum(1 for t in self)
|
||||
|
|
|
@ -34,20 +34,24 @@ class TestIter:
|
|||
print(repr(val))
|
||||
|
||||
def test_iter(self, db):
|
||||
assert list(db.config) == []
|
||||
db.config['spam'] = 'ham'
|
||||
db.config['eggs'] = 'bacon'
|
||||
assert set(db.config) == {'spam', 'eggs'}
|
||||
assert set(db.config.keys()) == {'spam', 'eggs'}
|
||||
assert set(db.config.values()) == {'ham', 'bacon'}
|
||||
assert set(db.config.items()) == {('spam', 'ham'), ('eggs', 'bacon')}
|
||||
def has_prefix(x):
|
||||
return x.startswith('TEST.')
|
||||
|
||||
assert [ x for x in db.config if has_prefix(x) ] == []
|
||||
db.config['TEST.spam'] = 'TEST.ham'
|
||||
db.config['TEST.eggs'] = 'TEST.bacon'
|
||||
assert { x for x in db.config if has_prefix(x) } == {'TEST.spam', 'TEST.eggs'}
|
||||
assert { x for x in db.config.keys() if has_prefix(x) } == {'TEST.spam', 'TEST.eggs'}
|
||||
assert { x for x in db.config.values() if has_prefix(x) } == {'TEST.ham', 'TEST.bacon'}
|
||||
assert { (x, y) for (x,y) in db.config.items() if has_prefix(x) } == \
|
||||
{('TEST.spam', 'TEST.ham'), ('TEST.eggs', 'TEST.bacon')}
|
||||
|
||||
def test_len(self, db):
|
||||
assert len(db.config) == 0
|
||||
defaults = len(db.config)
|
||||
db.config['spam'] = 'ham'
|
||||
assert len(db.config) == 1
|
||||
assert len(db.config) == defaults + 1
|
||||
db.config['eggs'] = 'bacon'
|
||||
assert len(db.config) == 2
|
||||
assert len(db.config) == defaults + 2
|
||||
|
||||
def test_del(self, db):
|
||||
db.config['spam'] = 'ham'
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# this file should be kept in sync with ../../../version
|
||||
__VERSION__ = '0.35'
|
||||
__VERSION__ = '0.36'
|
||||
SOVERSION = '5'
|
||||
|
|
35
configure
vendored
35
configure
vendored
|
@ -552,11 +552,7 @@ EOF
|
|||
rm -rf "$TEMP_GPG"
|
||||
fi
|
||||
|
||||
# see https://github.com/jstedfast/gmime/pull/90
|
||||
# should be fixed in GMime in 3.2.7, but some distros might patch
|
||||
printf "Checking for GMime X.509 certificate validity... "
|
||||
|
||||
cat > _check_x509_validity.c <<EOF
|
||||
cat > _check_gmime_cert.c <<EOF
|
||||
#include <stdio.h>
|
||||
#include <gmime/gmime.h>
|
||||
|
||||
|
@ -589,16 +585,27 @@ int main () {
|
|||
if (sig == NULL) return !! fprintf (stderr, "no GMimeSignature found at position 0\n");
|
||||
cert = g_mime_signature_get_certificate (sig);
|
||||
if (cert == NULL) return !! fprintf (stderr, "no GMimeCertificate found\n");
|
||||
#ifdef CHECK_VALIDITY
|
||||
validity = g_mime_certificate_get_id_validity (cert);
|
||||
if (validity != GMIME_VALIDITY_FULL) return !! fprintf (stderr, "Got validity %d, expected %d\n", validity, GMIME_VALIDITY_FULL);
|
||||
|
||||
#endif
|
||||
#ifdef CHECK_EMAIL
|
||||
const char *email = g_mime_certificate_get_email (cert);
|
||||
if (! email) return !! fprintf (stderr, "no email returned");
|
||||
if (email[0] == '<') return 2;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
# see https://github.com/jstedfast/gmime/pull/90
|
||||
# should be fixed in GMime in 3.2.7, but some distros might patch
|
||||
printf "Checking for GMime X.509 certificate validity... "
|
||||
|
||||
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))
|
||||
elif ${CC} ${CFLAGS} ${gmime_cflags} _check_x509_validity.c ${gmime_ldflags} -o _check_x509_validity \
|
||||
elif ${CC} -DCHECK_VALIDITY ${CFLAGS} ${gmime_cflags} _check_gmime_cert.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
|
||||
|
@ -620,6 +627,15 @@ EOF
|
|||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
printf "Checking whether GMime emits email addresses with angle brackets... "
|
||||
if ${CC} -DCHECK_EMAIL ${CFLAGS} ${gmime_cflags} _check_gmime_cert.c ${gmime_ldflags} -o _check_email &&
|
||||
GNUPGHOME=${TEMP_GPG} ./_check_email; then
|
||||
gmime_emits_angle_brackets=0
|
||||
printf "No.\n"
|
||||
else
|
||||
gmime_emits_angle_brackets=1
|
||||
printf "Yes.\n"
|
||||
fi
|
||||
else
|
||||
printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
|
||||
errors=$((errors + 1))
|
||||
|
@ -1256,7 +1272,7 @@ for flag in -Wmissing-declarations; do
|
|||
done
|
||||
printf "\n\t%s\n" "${WARN_CFLAGS}"
|
||||
|
||||
rm -f minimal minimal.c _time_t.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_x509_validity.c _check_x509_validity \
|
||||
rm -f minimal minimal.c _time_t.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_gmime_cert.c _check_x509_validity _check_email \
|
||||
_verify_sig_with_session_key.c _verify_sig_with_session_key
|
||||
|
||||
# construct the Makefile.config
|
||||
|
@ -1557,6 +1573,9 @@ NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${WITH_RETRY_LOCK}
|
|||
# Whether GMime can verify X.509 certificate validity
|
||||
NOTMUCH_GMIME_X509_CERT_VALIDITY=${gmime_x509_cert_validity}
|
||||
|
||||
# Whether GMime emits addresses with angle brackets (with <>)
|
||||
NOTMUCH_GMIME_EMITS_ANGLE_BRACKETS=${gmime_emits_angle_brackets}
|
||||
|
||||
# Whether GMime can verify signatures when decrypting with a session key:
|
||||
NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}
|
||||
|
||||
|
|
28
debian/changelog
vendored
28
debian/changelog
vendored
|
@ -1,3 +1,31 @@
|
|||
notmuch (0.36-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- David Bremner <bremner@debian.org> Mon, 25 Apr 2022 08:47:41 -0300
|
||||
|
||||
notmuch (0.36~rc1-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release candidate
|
||||
* Fix for build in environments where libsexp is not available
|
||||
(i.e. outside Debian).
|
||||
|
||||
-- David Bremner <bremner@debian.org> Sat, 16 Apr 2022 08:37:12 -0300
|
||||
|
||||
notmuch (0.36~rc0-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release candidate
|
||||
* Re-enable test smime.4, allegedly fixed upstream.
|
||||
|
||||
-- David Bremner <bremner@debian.org> Fri, 15 Apr 2022 08:45:10 -0300
|
||||
|
||||
notmuch (0.35-2) unstable; urgency=medium
|
||||
|
||||
* Disable test smime.4, which is broken by gmime 3.2.9 thanks to Lucas
|
||||
Nussbaum for the report (Closes: #1008462).
|
||||
|
||||
-- David Bremner <bremner@debian.org> Mon, 28 Mar 2022 11:45:11 -0600
|
||||
|
||||
notmuch (0.35-1~bpo11+2) bullseye-backports; urgency=medium
|
||||
|
||||
* Once more with binaries
|
||||
|
|
3
debian/rules
vendored
3
debian/rules
vendored
|
@ -15,9 +15,6 @@ override_dh_auto_configure:
|
|||
--zshcompletiondir=/usr/share/zsh/vendor-completions \
|
||||
--localstatedir=/var
|
||||
|
||||
override_dh_auto_test:
|
||||
dh_auto_test -- V=1
|
||||
|
||||
override_dh_auto_build:
|
||||
dh_auto_build -- V=1 all sphinx-html
|
||||
PYBUILD_NAME=notmuch dh_auto_build --buildsystem=pybuild --sourcedirectory bindings/python
|
||||
|
|
|
@ -1,58 +1,59 @@
|
|||
|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|
||||
| Key | Search Mode | Show Mode | Tree Mode |
|
||||
|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|
||||
| a | notmuch-search-archive-thread | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next |
|
||||
| b | notmuch-search-scroll-down | notmuch-show-resend-message | notmuch-show-resend-message |
|
||||
| c | notmuch-search-stash-map | notmuch-show-stash-map | notmuch-show-stash-map |
|
||||
| d | | | |
|
||||
| e | | | (notmuch-tree-button-activate) |
|
||||
| f | | notmuch-show-forward-message | notmuch-show-forward-message |
|
||||
| g | | | |
|
||||
| h | | notmuch-show-toggle-visibility-headers | |
|
||||
| i | | | |
|
||||
| j | notmuch-jump-search | notmuch-jump-search | notmuch-jump-search |
|
||||
| k | notmuch-tag-jump | notmuch-tag-jump | notmuch-tag-jump |
|
||||
| l | notmuch-search-filter | notmuch-show-filter-thread | notmuch-tree-filter |
|
||||
| m | notmuch-mua-new-mail | notmuch-mua-new-mail | notmuch-mua-new-mail |
|
||||
| n | notmuch-search-next-thread | notmuch-show-next-open-message | notmuch-tree-next-matching-message |
|
||||
| o | notmuch-search-toggle-order | | notmuch-tree-toggle-order |
|
||||
| p | notmuch-search-previous-thread | notmuch-show-previous-open-message | notmuch-tree-prev-matching-message |
|
||||
| q | notmuch-bury-or-kill-this-buffer | notmuch-bury-or-kill-this-buffer | notmuch-bury-or-kill-this-buffer |
|
||||
| r | notmuch-search-reply-to-thread-sender | notmuch-show-reply-sender | notmuch-show-reply-sender |
|
||||
| s | notmuch-search | notmuch-search | notmuch-search |
|
||||
| t | notmuch-search-filter-by-tag | toggle-truncate-lines | notmuch-tree-filter-by-tag |
|
||||
| u | | | |
|
||||
| v | | | notmuch-show-view-all-mime-parts |
|
||||
| w | | notmuch-show-save-attachments | notmuch-show-save-attachments |
|
||||
| x | notmuch-bury-or-kill-this-buffer | notmuch-show-archive-message-then-next-or-exit | notmuch-tree-quit |
|
||||
| y | | | |
|
||||
| z | notmuch-tree | notmuch-tree | notmuch-tree-to-tree |
|
||||
| A | | notmuch-show-archive-thread-then-next | notmuch-tree-archive-thread |
|
||||
| F | | notmuch-show-forward-open-messages | |
|
||||
| G | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer |
|
||||
| N | | notmuch-show-next-message | notmuch-tree-next-message |
|
||||
| O | | | |
|
||||
| P | | notmuch-show-previous-message | notmuch-tree-prev-message |
|
||||
| R | notmuch-search-reply-to-thread | notmuch-show-reply | notmuch-show-reply |
|
||||
| S | | | notmuch-search-from-tree-current-query |
|
||||
| V | | notmuch-show-view-raw-message | notmuch-show-view-raw-message |
|
||||
| X | | notmuch-show-archive-thread-then-exit | |
|
||||
| Z | notmuch-tree-from-search-current-query | notmuch-tree-from-show-current-query | |
|
||||
| =!= | | notmuch-show-toggle-elide-non-matching | |
|
||||
| =#= | | notmuch-show-print-message | |
|
||||
| =$= | | notmuch-show-toggle-process-crypto | |
|
||||
| =*= | notmuch-search-tag-all | notmuch-show-tag-all | notmuch-tree-tag-thread |
|
||||
| + | notmuch-search-add-tag | notmuch-show-add-tag | notmuch-tree-add-tag |
|
||||
| - | notmuch-search-remove-tag | notmuch-show-remove-tag | notmuch-tree-remove-tag |
|
||||
| . | | notmuch-show-part-map | |
|
||||
| < | notmuch-search-first-thread | notmuch-show-toggle-thread-indentation | |
|
||||
| <DEL> | notmuch-search-scroll-down | notmuch-show-rewind | notmuch-tree-scroll-message-window-back |
|
||||
| <RET> | notmuch-search-show-thread | notmuch-show-toggle-message | notmuch-tree-show-message |
|
||||
| <SPC> | notmuch-search-scroll-up | notmuch-show-advance | notmuch-tree-scroll-or-next |
|
||||
| <TAB> | | notmuch-show-next-button | notmuch-show-next-button |
|
||||
| <backtab> | | notmuch-show-previous-button | notmuch-show-previous-button |
|
||||
| = | notmuch-refresh-this-buffer | notmuch-refresh-this-buffer | notmuch-tree-refresh-view |
|
||||
| > | notmuch-search-last-thread | | |
|
||||
| ? | notmuch-help | notmuch-help | notmuch-help |
|
||||
| \vert | | notmuch-show-pipe-message | notmuch-show-pipe-message |
|
||||
|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|
||||
|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|
||||
| Key | Search Mode | Show Mode | Tree Mode |
|
||||
|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|
||||
| a | notmuch-search-archive-thread | notmuch-show-archive-message-then-next-or-next-thread | notmuch-tree-archive-message-then-next |
|
||||
| b | notmuch-search-scroll-down | notmuch-show-resend-message | notmuch-show-resend-message |
|
||||
| c | notmuch-search-stash-map | notmuch-show-stash-map | notmuch-show-stash-map |
|
||||
| d | | | |
|
||||
| e | | | (notmuch-tree-button-activate) |
|
||||
| f | | notmuch-show-forward-message | notmuch-show-forward-message |
|
||||
| g | | | |
|
||||
| h | | notmuch-show-toggle-visibility-headers | |
|
||||
| i | | | |
|
||||
| j | notmuch-jump-search | notmuch-jump-search | notmuch-jump-search |
|
||||
| k | notmuch-tag-jump | notmuch-tag-jump | notmuch-tag-jump |
|
||||
| l | notmuch-search-filter | notmuch-show-filter-thread | notmuch-tree-filter |
|
||||
| m | notmuch-mua-new-mail | notmuch-mua-new-mail | notmuch-mua-new-mail |
|
||||
| n | notmuch-search-next-thread | notmuch-show-next-open-message | notmuch-tree-next-matching-message |
|
||||
| o | notmuch-search-toggle-order | | notmuch-tree-toggle-order |
|
||||
| p | notmuch-search-previous-thread | notmuch-show-previous-open-message | notmuch-tree-prev-matching-message |
|
||||
| q | notmuch-bury-or-kill-this-buffer | notmuch-bury-or-kill-this-buffer | notmuch-bury-or-kill-this-buffer |
|
||||
| r | notmuch-search-reply-to-thread-sender | notmuch-show-reply-sender | notmuch-show-reply-sender |
|
||||
| s | notmuch-search | notmuch-search | notmuch-search |
|
||||
| t | notmuch-search-filter-by-tag | toggle-truncate-lines | notmuch-tree-filter-by-tag |
|
||||
| u | | | |
|
||||
| v | | | notmuch-show-view-all-mime-parts |
|
||||
| w | | notmuch-show-save-attachments | notmuch-show-save-attachments |
|
||||
| x | notmuch-bury-or-kill-this-buffer | notmuch-show-archive-message-then-next-or-exit | notmuch-tree-quit |
|
||||
| y | | | |
|
||||
| z | notmuch-tree | notmuch-tree | notmuch-tree-to-tree |
|
||||
| A | | notmuch-show-archive-thread-then-next | notmuch-tree-archive-thread |
|
||||
| F | | notmuch-show-forward-open-messages | |
|
||||
| G | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer | notmuch-poll-and-refresh-this-buffer |
|
||||
| N | | notmuch-show-next-message | notmuch-tree-next-message |
|
||||
| O | | | |
|
||||
| P | | notmuch-show-previous-message | notmuch-tree-prev-message |
|
||||
| R | notmuch-search-reply-to-thread | notmuch-show-reply | notmuch-show-reply |
|
||||
| S | | | notmuch-search-from-tree-current-query |
|
||||
| V | | notmuch-show-view-raw-message | notmuch-show-view-raw-message |
|
||||
| X | | notmuch-show-archive-thread-then-exit | |
|
||||
| Z | notmuch-tree-from-search-current-query | notmuch-tree-from-show-current-query | |
|
||||
| =!= | | notmuch-show-toggle-elide-non-matching | |
|
||||
| =#= | | notmuch-show-print-message | |
|
||||
| =$= | | notmuch-show-toggle-process-crypto | |
|
||||
| =*= | notmuch-search-tag-all | notmuch-show-tag-all | notmuch-tree-tag-thread |
|
||||
| + | notmuch-search-add-tag | notmuch-show-add-tag | notmuch-tree-add-tag |
|
||||
| - | notmuch-search-remove-tag | notmuch-show-remove-tag | notmuch-tree-remove-tag |
|
||||
| . | | notmuch-show-part-map | |
|
||||
| < | notmuch-search-first-thread | notmuch-show-toggle-thread-indentation | |
|
||||
| <DEL> | notmuch-search-scroll-down | notmuch-show-rewind | notmuch-tree-scroll-message-window-back |
|
||||
| <RET> | notmuch-search-show-thread | notmuch-show-toggle-message | notmuch-tree-show-message |
|
||||
| <SPC> | notmuch-search-scroll-up | notmuch-show-advance | notmuch-tree-scroll-or-next |
|
||||
| <TAB> | | notmuch-show-next-button | notmuch-show-next-button |
|
||||
| <backtab> | | notmuch-show-previous-button | notmuch-show-previous-button |
|
||||
| = | notmuch-refresh-this-buffer | notmuch-refresh-this-buffer | notmuch-tree-refresh-view |
|
||||
| > | notmuch-search-last-thread | | |
|
||||
| ? | notmuch-help | notmuch-help | notmuch-help |
|
||||
| \vert | | notmuch-show-pipe-message | notmuch-show-pipe-message |
|
||||
| [remap undo] | notmuch-tag-undo | notmuch-tag-undo | notmuch-tag-undo |
|
||||
|--------------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
|
||||
|
|
11
devel/notmuch-web/nmgunicorn.py
Normal file
11
devel/notmuch-web/nmgunicorn.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# to launch nmweb from gunicorn.
|
||||
|
||||
from nmweb import urls, index, search, show
|
||||
import web
|
||||
|
||||
app = web.application(urls, globals())
|
||||
|
||||
# get the wsgi app from web.py application object
|
||||
wsgiapp = app.wsgifunc()
|
366
devel/notmuch-web/nmweb.py
Executable file
366
devel/notmuch-web/nmweb.py
Executable file
|
@ -0,0 +1,366 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
from urllib.parse import quote_plus
|
||||
from urllib.parse import unquote_plus
|
||||
except ImportError:
|
||||
from urllib import quote_plus
|
||||
from urllib import unquote_plus
|
||||
|
||||
from datetime import datetime
|
||||
from mailbox import MaildirMessage
|
||||
import mimetypes
|
||||
import email
|
||||
import re
|
||||
import html
|
||||
import os
|
||||
|
||||
import bleach
|
||||
import web
|
||||
from notmuch2 import Database
|
||||
from jinja2 import Environment, FileSystemLoader # FIXME to PackageLoader
|
||||
from jinja2 import Markup
|
||||
try:
|
||||
import bjoern # from https://github.com/jonashaag/bjoern/
|
||||
use_bjoern = True
|
||||
except:
|
||||
use_bjoern = False
|
||||
|
||||
# Configuration options
|
||||
safe_tags = bleach.sanitizer.ALLOWED_TAGS + \
|
||||
[u'div', u'span', u'p', u'br', u'table', u'tr', u'td', u'th']
|
||||
linkify_plaintext = True # delays page load by about 0.02s of 0.20s budget
|
||||
show_thread_nav = True # delays page load by about 0.04s of 0.20s budget
|
||||
|
||||
prefix = os.environ.get('NMWEB_PREFIX', "http://localhost:8080")
|
||||
webprefix = os.environ.get('NMWEB_STATIC', prefix + "/static")
|
||||
cachedir = os.environ.get('NMWEB_CACHE', "static/cache") # special for webpy server; changeable if using your own
|
||||
cachepath = os.environ.get('NMWEB_CACHE_PATH', cachedir) # location of static cache in the local filesystem
|
||||
|
||||
if 'NMWEB_DEBUG' in os.environ:
|
||||
web.config.debug = True
|
||||
else:
|
||||
web.config.debug = False
|
||||
|
||||
# End of config options
|
||||
|
||||
env = Environment(autoescape=True,
|
||||
loader=FileSystemLoader('templates'))
|
||||
|
||||
urls = (
|
||||
'/', 'index',
|
||||
'/search/(.*)', 'search',
|
||||
'/show/(.*)', 'show',
|
||||
)
|
||||
|
||||
def urlencode_filter(s):
|
||||
if type(s) == 'Markup':
|
||||
s = s.unescape()
|
||||
s = s.encode('utf8')
|
||||
s = quote_plus(s)
|
||||
return Markup(s)
|
||||
env.filters['url'] = urlencode_filter
|
||||
|
||||
class index:
|
||||
def GET(self):
|
||||
web.header('Content-type', 'text/html')
|
||||
base = env.get_template('base.html')
|
||||
template = env.get_template('index.html')
|
||||
db = Database()
|
||||
tags = db.tags
|
||||
return template.render(tags=tags,
|
||||
title="Notmuch webmail",
|
||||
prefix=prefix,
|
||||
sprefix=webprefix)
|
||||
|
||||
class search:
|
||||
def GET(self, terms):
|
||||
redir = False
|
||||
if web.input(terms=None).terms:
|
||||
redir = True
|
||||
terms = web.input().terms
|
||||
terms = unquote_plus (terms)
|
||||
if web.input(afters=None).afters:
|
||||
afters = web.input(afters=None).afters[:-3]
|
||||
else:
|
||||
afters = '0'
|
||||
if web.input(befores=None).befores:
|
||||
befores = web.input(befores=None).befores
|
||||
else:
|
||||
befores = '4294967296' # 2^32
|
||||
try:
|
||||
if int(afters) > 0 or int(befores) < 4294967296:
|
||||
redir = True
|
||||
terms += ' date:@%s..@%s' % (int(afters), int(befores))
|
||||
except ValueError:
|
||||
pass
|
||||
if redir:
|
||||
raise web.seeother('/search/%s' % quote_plus(terms.encode('utf8')))
|
||||
web.header('Content-type', 'text/html')
|
||||
db = Database()
|
||||
ts = db.threads(query=terms, sort=Database.SORT.NEWEST_FIRST)
|
||||
template = env.get_template('search.html')
|
||||
return template.generate(terms=terms,
|
||||
ts=ts,
|
||||
title=terms,
|
||||
prefix=prefix,
|
||||
sprefix=webprefix)
|
||||
|
||||
def format_time_range(start, end):
|
||||
if end-start < (60*60*24):
|
||||
time = datetime.fromtimestamp(start).strftime('%Y %b %d %H:%M')
|
||||
else:
|
||||
start = datetime.fromtimestamp(start).strftime("%Y %b %d")
|
||||
end = datetime.fromtimestamp(end).strftime("%Y %b %d")
|
||||
time = "%s through %s" % (start, end)
|
||||
return time
|
||||
env.globals['format_time_range'] = format_time_range
|
||||
|
||||
def mailto_addrs(msg,header_name):
|
||||
try:
|
||||
hdr = msg.header(header_name)
|
||||
except LookupError:
|
||||
return ''
|
||||
|
||||
frm = email.utils.getaddresses([hdr])
|
||||
return ','.join(['<a href="mailto:%s">%s</a> ' % ((l, p) if p else (l, l)) for (p, l) in frm])
|
||||
env.globals['mailto_addrs'] = mailto_addrs
|
||||
|
||||
def link_msg(msg):
|
||||
lnk = quote_plus(msg.messageid.encode('utf8'))
|
||||
try:
|
||||
subj = msg.header('Subject')
|
||||
except LookupError:
|
||||
subj = ""
|
||||
out = '<a href="%s/show/%s">%s</a>' % (prefix, lnk, subj)
|
||||
return out
|
||||
env.globals['link_msg'] = link_msg
|
||||
|
||||
def show_msgs(msgs):
|
||||
r = '<ul>'
|
||||
for msg in msgs:
|
||||
red = 'color:black; font-style:normal'
|
||||
if msg.matched:
|
||||
red = 'color:red; font-style:italic'
|
||||
frm = mailto_addrs(msg,'From')
|
||||
lnk = link_msg(msg)
|
||||
tags = ", ".join(msg.tags)
|
||||
rs = show_msgs(msg.replies())
|
||||
r += '<li><span style="%s">%s—%s</span> [%s] %s</li>' % (red, frm, lnk, tags, rs)
|
||||
r += '</ul>'
|
||||
return r
|
||||
env.globals['show_msgs'] = show_msgs
|
||||
|
||||
# As email.message.walk, but showing close tags as well
|
||||
def mywalk(self):
|
||||
yield self
|
||||
if self.is_multipart():
|
||||
for subpart in self.get_payload():
|
||||
for subsubpart in mywalk(subpart):
|
||||
yield subsubpart
|
||||
yield 'close-div'
|
||||
|
||||
class show:
|
||||
def GET(self, mid):
|
||||
web.header('Content-type', 'text/html')
|
||||
db = Database()
|
||||
try:
|
||||
m = db.find(mid)
|
||||
except:
|
||||
raise web.notfound("No such message id.")
|
||||
template = env.get_template('show.html')
|
||||
# FIXME add reply-all link with email.urils.getaddresses
|
||||
# FIXME add forward link using mailto with body parameter?
|
||||
return template.render(m=m,
|
||||
mid=mid,
|
||||
title=m.header('Subject'),
|
||||
prefix=prefix,
|
||||
sprefix=webprefix)
|
||||
|
||||
def thread_nav(m):
|
||||
if not show_thread_nav: return
|
||||
db = Database()
|
||||
thread = next(db.threads('thread:'+m.threadid))
|
||||
prv = None
|
||||
found = False
|
||||
nxt = None
|
||||
for msg in thread:
|
||||
if m == msg:
|
||||
found = True
|
||||
elif not found:
|
||||
prv = msg
|
||||
else: # found message, but not on this loop
|
||||
nxt = msg
|
||||
break
|
||||
yield "<hr><ul>"
|
||||
if prv: yield "<li>Previous message (by thread): %s</li>" % link_msg(prv)
|
||||
if nxt: yield "<li>Next message (by thread): %s</li>" % link_msg(nxt)
|
||||
yield "</ul><h3>Thread:</h3>"
|
||||
# FIXME show now takes three queries instead of 1;
|
||||
# can we yield the message body while computing the thread shape?
|
||||
thread = next(db.threads('thread:'+m.threadid))
|
||||
yield show_msgs(thread.toplevel())
|
||||
return
|
||||
env.globals['thread_nav'] = thread_nav
|
||||
|
||||
def format_message(nm_msg, mid):
|
||||
fn = list(nm_msg.filenames())[0]
|
||||
msg = MaildirMessage(open(fn))
|
||||
return format_message_walk(msg, mid)
|
||||
|
||||
def decodeAnyway(txt, charset='ascii'):
|
||||
try:
|
||||
out = txt.decode(charset)
|
||||
except:
|
||||
try:
|
||||
out = txt.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
out = txt.decode('latin1')
|
||||
return out
|
||||
|
||||
def require_protocol_prefix(attrs, new=False):
|
||||
if not new:
|
||||
return attrs
|
||||
link_text = attrs[u'_text']
|
||||
if link_text.startswith(('http:', 'https:', 'mailto:', 'git:', 'id:')):
|
||||
return attrs
|
||||
return None
|
||||
|
||||
# Bleach doesn't even try to linkify id:... text, so no point invoking this yet
|
||||
def modify_id_links(attrs, new=False):
|
||||
if attrs[(None, u'href')].startswith(u'id:'):
|
||||
attrs[(None, u'href')] = prefix + "/show/" + attrs[(None, u'href')][3:]
|
||||
return attrs
|
||||
|
||||
def css_part_id(content_type, parts=[]):
|
||||
c = content_type.replace('/', '-')
|
||||
out = "-".join(parts + [c])
|
||||
return out
|
||||
|
||||
def format_message_walk(msg, mid):
|
||||
counter = 0
|
||||
cid_refd = []
|
||||
parts = ['main']
|
||||
for part in mywalk(msg):
|
||||
if part == 'close-div':
|
||||
parts.pop()
|
||||
yield '</div>'
|
||||
elif part.get_content_maintype() == 'multipart':
|
||||
yield '<div class="multipart-%s" id="%s">' % \
|
||||
(part.get_content_subtype(), css_part_id(part.get_content_type(), parts))
|
||||
parts.append(part.get_content_subtype())
|
||||
if part.get_content_subtype() == 'alternative':
|
||||
yield '<ul>'
|
||||
for subpart in part.get_payload():
|
||||
yield ('<li><a href="#%s">%s</a></li>' %
|
||||
(css_part_id(subpart.get_content_type(), parts),
|
||||
subpart.get_content_type()))
|
||||
yield '</ul>'
|
||||
elif part.get_content_type() == 'message/rfc822':
|
||||
# FIXME extract subject, date, to/cc/from into a separate template and use it here
|
||||
yield '<div class="message-rfc822">'
|
||||
elif part.get_content_maintype() == 'text':
|
||||
if part.get_content_subtype() == 'plain':
|
||||
yield '<div id="%s">' % css_part_id(part.get_content_type(), parts)
|
||||
yield '<pre>'
|
||||
out = part.get_payload(decode=True)
|
||||
out = decodeAnyway(out, part.get_content_charset('ascii'))
|
||||
out = html.escape(out)
|
||||
out = out.encode('ascii', 'xmlcharrefreplace').decode('ascii')
|
||||
if linkify_plaintext: out = bleach.linkify(out, callbacks=[require_protocol_prefix])
|
||||
yield out
|
||||
yield '</pre></div>'
|
||||
elif part.get_content_subtype() == 'html':
|
||||
yield '<div id="%s">' % css_part_id(part.get_content_type(), parts)
|
||||
unb64 = part.get_payload(decode=True)
|
||||
decoded = decodeAnyway(unb64, part.get_content_charset('ascii'))
|
||||
cid_refd += find_cids(decoded)
|
||||
part.set_payload(bleach.clean(replace_cids(decoded, mid), tags=safe_tags).
|
||||
encode(part.get_content_charset('ascii'), 'xmlcharrefreplace'))
|
||||
(filename, cid) = link_to_cached_file(part, mid, counter)
|
||||
counter += 1
|
||||
yield '<iframe class="embedded-html" src="%s"></iframe>' % \
|
||||
os.path.join(prefix, cachedir, mid, filename)
|
||||
yield '</div>'
|
||||
else:
|
||||
yield '<div id="%s">' % css_part_id(part.get_content_type(), parts)
|
||||
(filename, cid) = link_to_cached_file(part, mid, counter)
|
||||
counter += 1
|
||||
yield '<a href="%s">%s (%s)</a>' % (os.path.join(prefix,
|
||||
cachedir,
|
||||
mid,
|
||||
filename),
|
||||
filename,
|
||||
part.get_content_type())
|
||||
yield '</div>'
|
||||
elif part.get_content_maintype() == 'image':
|
||||
(filename, cid) = link_to_cached_file(part, mid, counter)
|
||||
if cid not in cid_refd:
|
||||
counter += 1
|
||||
yield '<img src="%s" alt="%s">' % (os.path.join(prefix,
|
||||
cachedir,
|
||||
mid,
|
||||
filename),
|
||||
filename)
|
||||
else:
|
||||
(filename, cid) = link_to_cached_file(part, mid, counter)
|
||||
counter += 1
|
||||
yield '<a href="%s">%s (%s)</a>' % (os.path.join(prefix,
|
||||
cachedir,
|
||||
mid,
|
||||
filename),
|
||||
filename,
|
||||
part.get_content_type())
|
||||
env.globals['format_message'] = format_message
|
||||
|
||||
def replace_cids(body, mid):
|
||||
return body.replace('cid:', os.path.join(prefix, cachedir, mid)+'/')
|
||||
|
||||
def find_cids(body):
|
||||
return re.findall(r'cid:([^ "\'>]*)', body)
|
||||
|
||||
def link_to_cached_file(part, mid, counter):
|
||||
filename = part.get_filename()
|
||||
if not filename:
|
||||
ext = mimetypes.guess_extension(part.get_content_type())
|
||||
if not ext:
|
||||
ext = '.bin'
|
||||
filename = 'part-%03d%s' % (counter, ext)
|
||||
try:
|
||||
os.makedirs(os.path.join(cachepath, mid))
|
||||
except OSError:
|
||||
pass
|
||||
fn = os.path.join(cachepath, mid, filename) # FIXME escape mid, filename
|
||||
fp = open(fn, 'wb')
|
||||
if part.get_content_maintype() == 'text':
|
||||
data = part.get_payload(decode=True)
|
||||
data = decodeAnyway(data, part.get_content_charset('ascii')).encode('utf-8')
|
||||
else:
|
||||
try:
|
||||
data = part.get_payload(decode=True)
|
||||
except:
|
||||
data = part.get_payload(decode=False)
|
||||
if data:
|
||||
fp.write(data)
|
||||
fp.close()
|
||||
if 'Content-ID' in part:
|
||||
cid = part['Content-ID']
|
||||
if cid[0] == '<' and cid[-1] == '>': cid = cid[1:-1]
|
||||
cid_fn = os.path.join(cachepath, mid, cid) # FIXME escape mid, cid
|
||||
try:
|
||||
os.unlink(cid_fn)
|
||||
except OSError:
|
||||
pass
|
||||
os.link(fn, cid_fn)
|
||||
return (filename, cid)
|
||||
else:
|
||||
return (filename, None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = web.application(urls, globals())
|
||||
if use_bjoern:
|
||||
bjoern.run(app.wsgifunc(), "127.0.0.1", 8080)
|
||||
else:
|
||||
app.run()
|
1
devel/notmuch-web/static/css/jquery-ui.css
vendored
Symbolic link
1
devel/notmuch-web/static/css/jquery-ui.css
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
/usr/share/javascript/jquery-ui/themes/base/jquery-ui.min.css
|
15
devel/notmuch-web/static/css/notmuch-0.1.css
Normal file
15
devel/notmuch-web/static/css/notmuch-0.1.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-rfc822 {
|
||||
border: 1px solid;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.embedded-html {
|
||||
frameborder: 0;
|
||||
border: 0;
|
||||
scrolling: no;
|
||||
width: 100%;
|
||||
}
|
1
devel/notmuch-web/static/js/jquery-ui.js
vendored
Symbolic link
1
devel/notmuch-web/static/js/jquery-ui.js
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
/usr/share/javascript/jquery-ui/jquery-ui.min.js
|
1
devel/notmuch-web/static/js/jquery.js
vendored
Symbolic link
1
devel/notmuch-web/static/js/jquery.js
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
/usr/share/javascript/jquery/jquery.min.js
|
35
devel/notmuch-web/static/js/notmuch-0.1.js
Normal file
35
devel/notmuch-web/static/js/notmuch-0.1.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
$(function(){
|
||||
$("#after").datepicker({
|
||||
altField: "#afters",
|
||||
altFormat: "@",
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
defaultDate: "-7d",
|
||||
minDate: "01/01/1970",
|
||||
yearRange: "2000:+0",
|
||||
onSelect: function(selectedDate) {
|
||||
$("#before").datepicker("option","minDate",selectedDate);
|
||||
}
|
||||
});
|
||||
$("#before").datepicker({
|
||||
altField: "#befores",
|
||||
altFormat: "@",
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
defaultDate: "+1d",
|
||||
maxDate: "+1d",
|
||||
yearRange: "2000:+0",
|
||||
onSelect: function(selectedDate) {
|
||||
$("#after").datepicker("option","maxDate",selectedDate);
|
||||
}
|
||||
});
|
||||
$(function(){
|
||||
$('.multipart-alternative').tabs()
|
||||
});
|
||||
$(function(){
|
||||
$('.embedded-html').on('load',function(){
|
||||
this.style.height = this.contentWindow.document.body.offsetHeight + 'px';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
39
devel/notmuch-web/templates/base.html
Normal file
39
devel/notmuch-web/templates/base.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link type="text/css" href="{{sprefix}}/css/jquery-ui.css" rel="stylesheet" />
|
||||
<link type="text/css" href="{{sprefix}}/css/notmuch-0.1.css" rel="stylesheet" />
|
||||
<script type="text/javascript" src="{{sprefix}}/js/jquery.js"></script>
|
||||
<script type="text/javascript" src="{{sprefix}}/js/jquery-ui.js"></script>
|
||||
<script type="text/javascript" src="{{sprefix}}/js/notmuch-0.1.js"></script>
|
||||
<title>{{title}}</title>
|
||||
</head><body>
|
||||
<div data-role="page">
|
||||
<div data-role="header">
|
||||
{% block searchform %}
|
||||
<form action="{{prefix}}/search/" method="GET" data-ajax="false">
|
||||
<label for="terms">Terms</label><input id="terms" name="terms">
|
||||
<label for="after">After</label><input id="after"
|
||||
name="after"><input type="hidden" id="afters" name="afters">
|
||||
<label for="before">Before</label><input id="before"
|
||||
name="before"><input id="befores" type="hidden" name="befores">
|
||||
<input type="submit" name="submit" id="submit" value="Search">
|
||||
</form>
|
||||
{% endblock searchform %}
|
||||
<h2>{{title}}</h2>
|
||||
</div>
|
||||
<div data-role="content">
|
||||
{% block content %}
|
||||
<h2>Common tags</h2>
|
||||
<ul>
|
||||
{% for tag in tags %}
|
||||
<li><a href="search/tag:{{ tag|url }}">{{ tag|e }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
</div>
|
||||
</body></html>
|
9
devel/notmuch-web/templates/index.html
Normal file
9
devel/notmuch-web/templates/index.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h2>Common tags</h2>
|
||||
<ul>
|
||||
{% for tag in tags %}
|
||||
<li><a href="search/tag:{{ tag|url }}">{{ tag|e }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
10
devel/notmuch-web/templates/search.html
Normal file
10
devel/notmuch-web/templates/search.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
<h1>{{ terms|e }}</h1>
|
||||
{% block content %}
|
||||
{% for t in ts %}
|
||||
<h2>{{ t.subject|e }}</h2>
|
||||
<p><i>{{ t.authors|e }}</i></p>
|
||||
<p><b>{{ format_time_range(t.first,t.last)|e }}</b></p>
|
||||
{{ show_msgs(t.toplevel())|safe }}
|
||||
{% endfor %}
|
||||
{% endblock content %}
|
15
devel/notmuch-web/templates/show.html
Normal file
15
devel/notmuch-web/templates/show.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% set headers = ['Subject', 'Date'] %}
|
||||
{% set addr_headers = ['To', 'Cc', 'From'] %}
|
||||
{% for header in headers: %}
|
||||
<p><b>{{header}}:</b>{{m.header(header)|e}}</p>
|
||||
{% endfor %}
|
||||
{% for header in addr_headers: %}
|
||||
<p><b>{{header}}:</b>{{mailto_addrs(m,header)|safe}}</p>
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for part in format_message(m,mid): %}{{ part|safe }}{% endfor %}
|
||||
{% for b in thread_nav(m): %}{{b|safe}}{% endfor %}
|
||||
<hr>
|
||||
{% endblock content %}
|
14
devel/notmuch-web/todo
Normal file
14
devel/notmuch-web/todo
Normal file
|
@ -0,0 +1,14 @@
|
|||
review escaping and safety handling mail from Bad People
|
||||
|
||||
revise template loader---can we make this faster?
|
||||
|
||||
add reply-all link with email.urils.getaddresses
|
||||
|
||||
change current reply links to quote body
|
||||
|
||||
add forward link using mailto with body parameter?
|
||||
|
||||
unescape the current search term, including translating back dates
|
||||
|
||||
|
||||
later: json support, iOS app?
|
|
@ -59,7 +59,7 @@ readonly VERSION
|
|||
|
||||
# In the rest of this file, tests collect list of errors to be fixed
|
||||
|
||||
echo -n "Checking that git working directory is clean... "
|
||||
printf %s "Checking that git working directory is clean... "
|
||||
git_status=`git status --porcelain`
|
||||
if [ "$git_status" = '' ]
|
||||
then
|
||||
|
@ -77,7 +77,7 @@ verfail ()
|
|||
append_emsg " Please follow the instructions in RELEASING to choose a version"
|
||||
}
|
||||
|
||||
echo -n "Checking that '$VERSION' is good with digits and periods... "
|
||||
printf %s "Checking that '$VERSION' is good with digits and periods... "
|
||||
case $VERSION in
|
||||
*[!0-9.]*)
|
||||
verfail "'$VERSION' contains other characters than digits and periods" ;;
|
||||
|
@ -88,7 +88,7 @@ case $VERSION in
|
|||
*) verfail "'$VERSION' is a single number" ;;
|
||||
esac
|
||||
|
||||
echo -n "Checking that this is Debian package for notmuch... "
|
||||
printf %s "Checking that this is Debian package for notmuch... "
|
||||
read deb_notmuch deb_version rest < debian/changelog
|
||||
if [ "$deb_notmuch" = 'notmuch' ]
|
||||
then
|
||||
|
@ -98,7 +98,7 @@ else
|
|||
append_emsg "Package name '$deb_notmuch' is not 'notmuch' in debian/changelog"
|
||||
fi
|
||||
|
||||
echo -n "Checking that Debian package version is $VERSION-1... "
|
||||
printf %s "Checking that Debian package version is $VERSION-1... "
|
||||
|
||||
if [ "$deb_version" = "($VERSION-1)" ]
|
||||
then
|
||||
|
@ -108,7 +108,7 @@ else
|
|||
append_emsg "Version '$deb_version' is not '($VERSION-1)' in debian/changelog"
|
||||
fi
|
||||
|
||||
echo -n "Checking that python bindings version is $VERSION... "
|
||||
printf %s "Checking that python bindings version is $VERSION... "
|
||||
py_version=`python3 -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
|
||||
if [ "$py_version" = "$VERSION" ]
|
||||
then
|
||||
|
@ -118,7 +118,7 @@ else
|
|||
append_emsg "Version '$py_version' is not '$VERSION' in $PV_FILE"
|
||||
fi
|
||||
|
||||
echo -n "Checking that NEWS header is tidy... "
|
||||
printf %s "Checking that NEWS header is tidy... "
|
||||
if [ "`exec sed 's/./=/g; 1q' NEWS`" = "`exec sed '1d; 2q' NEWS`" ]
|
||||
then
|
||||
echo Yes.
|
||||
|
@ -132,7 +132,7 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
echo -n "Checking that this is Notmuch NEWS... "
|
||||
printf %s "Checking that this is Notmuch NEWS... "
|
||||
read news_notmuch news_version news_date < NEWS
|
||||
if [ "$news_notmuch" = "Notmuch" ]
|
||||
then
|
||||
|
@ -142,7 +142,7 @@ else
|
|||
append_emsg "First word '$news_notmuch' is not 'Notmuch' in NEWS file"
|
||||
fi
|
||||
|
||||
echo -n "Checking that NEWS version is $VERSION... "
|
||||
printf %s "Checking that NEWS version is $VERSION... "
|
||||
if [ "$news_version" = "$VERSION" ]
|
||||
then
|
||||
echo Yes.
|
||||
|
@ -154,7 +154,7 @@ fi
|
|||
#eval `date '+year=%Y mon=%m day=%d'`
|
||||
today0utc=`date --date=0Z +%s` # gnu date feature
|
||||
|
||||
echo -n "Checking that NEWS date is right... "
|
||||
printf %s "Checking that NEWS date is right... "
|
||||
case $news_date in
|
||||
'('[2-9][0-9][0-9][0-9]-[01][0-9]-[0123][0-9]')')
|
||||
newsdate0utc=`nd=${news_date#\\(}; date --date="${nd%)} 0Z" +%s`
|
||||
|
@ -176,7 +176,7 @@ case $news_date in
|
|||
esac
|
||||
|
||||
year=`exec date +%Y`
|
||||
echo -n "Checking that copyright in documentation contains 2009-$year... "
|
||||
printf %s "Checking that copyright in documentation contains 2009-$year... "
|
||||
# Read the value of variable `copyright' defined in 'doc/conf.py'.
|
||||
copyrightline=$(grep ^copyright doc/conf.py)
|
||||
case $copyrightline in
|
||||
|
|
|
@ -55,22 +55,19 @@ The available configuration items are described below. Non-absolute
|
|||
paths are presumed relative to `$HOME` for items in section
|
||||
**database**.
|
||||
|
||||
database.path
|
||||
Notmuch will store its database here, (in
|
||||
sub-directory named ``.notmuch`` if **database.mail\_root**
|
||||
is unset).
|
||||
built_with.<name>
|
||||
Compile time feature <name>. Current possibilities include
|
||||
"retry_lock" (configure option, included by default).
|
||||
(since notmuch 0.30, "compact" and "field_processor" are
|
||||
always included.)
|
||||
|
||||
Default: see :ref:`database`
|
||||
database.autocommit
|
||||
|
||||
database.mail_root
|
||||
The top-level directory where your mail currently exists and to
|
||||
where mail will be delivered in the future. Files should be
|
||||
individual email messages.
|
||||
How often to commit transactions to disk. `0` means wait until
|
||||
command completes, otherwise an integer `n` specifies to commit to
|
||||
disk after every `n` completed transactions.
|
||||
|
||||
History: this configuration value was introduced in notmuch 0.32.
|
||||
|
||||
Default: For compatibility with older configurations, the value of
|
||||
database.path is used if **database.mail\_root** is unset.
|
||||
History: this configuration value was introduced in notmuch 0.33.
|
||||
|
||||
database.backup_dir
|
||||
Directory to store tag dumps when upgrading database.
|
||||
|
@ -88,109 +85,26 @@ database.hook_dir
|
|||
|
||||
Default: See HOOKS, below.
|
||||
|
||||
database.autocommit
|
||||
.. _database.mail_root:
|
||||
|
||||
How often to commit transactions to disk. `0` means wait until
|
||||
command completes, otherwise an integer `n` specifies to commit to
|
||||
disk after every `n` completed transactions.
|
||||
database.mail_root
|
||||
The top-level directory where your mail currently exists and to
|
||||
where mail will be delivered in the future. Files should be
|
||||
individual email messages.
|
||||
|
||||
History: this configuration value was introduced in notmuch 0.33.
|
||||
History: this configuration value was introduced in notmuch 0.32.
|
||||
|
||||
user.name
|
||||
Your full name.
|
||||
Default: For compatibility with older configurations, the value of
|
||||
database.path is used if **database.mail\_root** is unset.
|
||||
|
||||
Default: ``$NAME`` variable if set, otherwise read from
|
||||
``/etc/passwd``.
|
||||
database.path
|
||||
Notmuch will store its database here, (in
|
||||
sub-directory named ``.notmuch`` if **database.mail\_root**
|
||||
is unset).
|
||||
|
||||
user.primary\_email
|
||||
Your primary email address.
|
||||
Default: see :ref:`database`
|
||||
|
||||
Default: ``$EMAIL`` variable if set, otherwise constructed from
|
||||
the username and hostname of the current machine.
|
||||
|
||||
user.other\_email
|
||||
A list of other email addresses at which you receive email.
|
||||
|
||||
Default: not set.
|
||||
|
||||
new.tags
|
||||
A list of tags that will be added to all messages incorporated by
|
||||
**notmuch new**.
|
||||
|
||||
Default: ``unread;inbox``.
|
||||
|
||||
new.ignore
|
||||
A list to specify files and directories that will not be searched
|
||||
for messages by :any:`notmuch-new(1)`. Each entry in the list is either:
|
||||
|
||||
A file or a directory name, without path, that will be ignored,
|
||||
regardless of the location in the mail store directory hierarchy.
|
||||
|
||||
Or:
|
||||
|
||||
A regular expression delimited with // that will be matched
|
||||
against the path of the file or directory relative to the database
|
||||
path. Matching files and directories will be ignored. The
|
||||
beginning and end of string must be explicitly anchored. For
|
||||
example, /.*/foo$/ would match "bar/foo" and "bar/baz/foo", but
|
||||
not "foo" or "bar/foobar".
|
||||
|
||||
Default: empty list.
|
||||
|
||||
search.exclude\_tags
|
||||
A list of tags that will be excluded from search results by
|
||||
default. Using an excluded tag in a query will override that
|
||||
exclusion.
|
||||
|
||||
Default: empty list. Note that :any:`notmuch-setup(1)` puts
|
||||
``deleted;spam`` here when creating new configuration file.
|
||||
|
||||
.. _show.extra_headers:
|
||||
|
||||
show.extra\_headers
|
||||
|
||||
By default :any:`notmuch-show(1)` includes the following headers
|
||||
in structured output if they are present in the message:
|
||||
`Subject`, `From`, `To`, `Cc`, `Bcc`, `Reply-To`, `Date`. This
|
||||
option allows the specification of a list of further
|
||||
headers to output.
|
||||
|
||||
History: This configuration value was introduced in notmuch 0.35.
|
||||
|
||||
Default: empty list.
|
||||
|
||||
maildir.synchronize\_flags
|
||||
If true, then the following maildir flags (in message filenames)
|
||||
will be synchronized with the corresponding notmuch tags:
|
||||
|
||||
+--------+-----------------------------------------------+
|
||||
| Flag | Tag |
|
||||
+========+===============================================+
|
||||
| D | draft |
|
||||
+--------+-----------------------------------------------+
|
||||
| F | flagged |
|
||||
+--------+-----------------------------------------------+
|
||||
| P | passed |
|
||||
+--------+-----------------------------------------------+
|
||||
| R | replied |
|
||||
+--------+-----------------------------------------------+
|
||||
| S | unread (added when 'S' flag is not present) |
|
||||
+--------+-----------------------------------------------+
|
||||
|
||||
The :any:`notmuch-new(1)` command will notice flag changes in
|
||||
filenames and update tags, while the :any:`notmuch-tag(1)` and
|
||||
:any:`notmuch-restore(1)` commands will notice tag changes and
|
||||
update flags in filenames.
|
||||
|
||||
If there have been any changes in the maildir (new messages added,
|
||||
old ones removed or renamed, maildir flags changed, etc.), it is
|
||||
advisable to run :any:`notmuch-new(1)` before
|
||||
:any:`notmuch-tag(1)` or :any:`notmuch-restore(1)` commands to
|
||||
ensure the tag changes are properly synchronized to the maildir
|
||||
flags, as the commands expect the database and maildir to be in
|
||||
sync.
|
||||
|
||||
Default: ``true``.
|
||||
.. _index.decrypt:
|
||||
|
||||
index.decrypt
|
||||
Policy for decrypting encrypted messages during indexing. Must be
|
||||
|
@ -245,6 +159,8 @@ index.decrypt
|
|||
|
||||
Default: ``auto``.
|
||||
|
||||
.. _index.header:
|
||||
|
||||
index.header.<prefix>
|
||||
Define the query prefix <prefix>, based on a mail header. For
|
||||
example ``index.header.List=List-Id`` will add a probabilistic
|
||||
|
@ -254,22 +170,121 @@ index.header.<prefix>
|
|||
supported. See :any:`notmuch-search-terms(7)` for a list of existing
|
||||
prefixes, and an explanation of probabilistic prefixes.
|
||||
|
||||
built_with.<name>
|
||||
Compile time feature <name>. Current possibilities include
|
||||
"retry_lock" (configure option, included by default).
|
||||
(since notmuch 0.30, "compact" and "field_processor" are
|
||||
always included.)
|
||||
.. _maildir.synchronize_flags:
|
||||
|
||||
maildir.synchronize\_flags
|
||||
If true, then the following maildir flags (in message filenames)
|
||||
will be synchronized with the corresponding notmuch tags:
|
||||
|
||||
+--------+-----------------------------------------------+
|
||||
| Flag | Tag |
|
||||
+========+===============================================+
|
||||
| D | draft |
|
||||
+--------+-----------------------------------------------+
|
||||
| F | flagged |
|
||||
+--------+-----------------------------------------------+
|
||||
| P | passed |
|
||||
+--------+-----------------------------------------------+
|
||||
| R | replied |
|
||||
+--------+-----------------------------------------------+
|
||||
| S | unread (added when 'S' flag is not present) |
|
||||
+--------+-----------------------------------------------+
|
||||
|
||||
The :any:`notmuch-new(1)` command will notice flag changes in
|
||||
filenames and update tags, while the :any:`notmuch-tag(1)` and
|
||||
:any:`notmuch-restore(1)` commands will notice tag changes and
|
||||
update flags in filenames.
|
||||
|
||||
If there have been any changes in the maildir (new messages added,
|
||||
old ones removed or renamed, maildir flags changed, etc.), it is
|
||||
advisable to run :any:`notmuch-new(1)` before
|
||||
:any:`notmuch-tag(1)` or :any:`notmuch-restore(1)` commands to
|
||||
ensure the tag changes are properly synchronized to the maildir
|
||||
flags, as the commands expect the database and maildir to be in
|
||||
sync.
|
||||
|
||||
Default: ``true``.
|
||||
|
||||
.. _new.ignore:
|
||||
|
||||
new.ignore
|
||||
A list to specify files and directories that will not be searched
|
||||
for messages by :any:`notmuch-new(1)`. Each entry in the list is either:
|
||||
|
||||
A file or a directory name, without path, that will be ignored,
|
||||
regardless of the location in the mail store directory hierarchy.
|
||||
|
||||
Or:
|
||||
|
||||
A regular expression delimited with // that will be matched
|
||||
against the path of the file or directory relative to the database
|
||||
path. Matching files and directories will be ignored. The
|
||||
beginning and end of string must be explicitly anchored. For
|
||||
example, /.*/foo$/ would match "bar/foo" and "bar/baz/foo", but
|
||||
not "foo" or "bar/foobar".
|
||||
|
||||
Default: empty list.
|
||||
|
||||
.. _new.tags:
|
||||
|
||||
new.tags
|
||||
A list of tags that will be added to all messages incorporated by
|
||||
**notmuch new**.
|
||||
|
||||
Default: ``unread;inbox``.
|
||||
|
||||
query.<name>
|
||||
Expansion for named query called <name>. See
|
||||
:any:`notmuch-search-terms(7)` for more information about named
|
||||
queries.
|
||||
|
||||
search.exclude\_tags
|
||||
A list of tags that will be excluded from search results by
|
||||
default. Using an excluded tag in a query will override that
|
||||
exclusion.
|
||||
|
||||
Default: empty list. Note that :any:`notmuch-setup(1)` puts
|
||||
``deleted;spam`` here when creating new configuration file.
|
||||
|
||||
.. _show.extra_headers:
|
||||
|
||||
show.extra\_headers
|
||||
|
||||
By default :any:`notmuch-show(1)` includes the following headers
|
||||
in structured output if they are present in the message:
|
||||
`Subject`, `From`, `To`, `Cc`, `Bcc`, `Reply-To`, `Date`. This
|
||||
option allows the specification of a list of further
|
||||
headers to output.
|
||||
|
||||
History: This configuration value was introduced in notmuch 0.35.
|
||||
|
||||
Default: empty list.
|
||||
|
||||
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.
|
||||
|
||||
user.name
|
||||
Your full name.
|
||||
|
||||
Default: ``$NAME`` variable if set, otherwise read from
|
||||
``/etc/passwd``.
|
||||
|
||||
user.other\_email
|
||||
A list of other email addresses at which you receive email
|
||||
(see also, :ref:`user.primary_email <user.primary_email>`).
|
||||
|
||||
Default: not set.
|
||||
|
||||
.. _user.primary_email:
|
||||
|
||||
user.primary\_email
|
||||
Your primary email address.
|
||||
|
||||
Default: ``$EMAIL`` variable if set, otherwise constructed from
|
||||
the username and hostname of the current machine.
|
||||
|
||||
FILES
|
||||
=====
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ DESCRIPTION
|
|||
|
||||
**notmuch insert** reads a message from standard input and delivers it
|
||||
into the maildir directory given by configuration option
|
||||
**database.mail_root**, then incorporates the message into the notmuch
|
||||
:ref:`database.mail_root <database.mail_root>`, then incorporates the message into the notmuch
|
||||
database. It is an alternative to using a separate tool to deliver the
|
||||
message then running :any:`notmuch-new(1)` afterwards.
|
||||
|
||||
The new message will be tagged with the tags specified by the
|
||||
**new.tags** configuration option, then by operations specified on the
|
||||
:ref:`new.tags <new.tags>` configuration option, then by operations specified on the
|
||||
command-line: tags prefixed by '+' are added while those prefixed by '-'
|
||||
are removed.
|
||||
|
||||
|
@ -86,7 +86,17 @@ Supported options for **insert** include
|
|||
``--decrypt=nostash`` without considering the security of your
|
||||
index.
|
||||
|
||||
See also ``index.decrypt`` in :any:`notmuch-config(1)`.
|
||||
See also :ref:`index.decrypt <index.decrypt>` in :any:`notmuch-config(1)`.
|
||||
|
||||
CONFIGURATION
|
||||
=============
|
||||
|
||||
Indexing is influenced by the configuration options
|
||||
:ref:`index.decrypt <index.decrypt>` and :ref:`index.header
|
||||
<index.header>`. Tagging
|
||||
is controlled by :ref:`new.tags <new.tags>` and
|
||||
:ref:`maildir.synchronize_flags <maildir.synchronize_flags>`. See
|
||||
:any:`notmuch-config(1)` for details.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
|
|
@ -78,6 +78,16 @@ Supported options for **new** include
|
|||
to optimize the scanning of directories for new mail. This option turns
|
||||
that optimization off.
|
||||
|
||||
CONFIGURATION
|
||||
=============
|
||||
|
||||
Indexing is influenced by the configuration options
|
||||
:ref:`index.decrypt <index.decrypt>`, :ref:`index.header
|
||||
<index.header>`, and :ref:`new.ignore <new.ignore>`. Tagging
|
||||
is controlled by :ref:`new.tags <new.tags>` and
|
||||
:ref:`maildir.synchronize_flags <maildir.synchronize_flags>`. See
|
||||
:any:`notmuch-config(1)` for details.
|
||||
|
||||
EXIT STATUS
|
||||
===========
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ pre-new
|
|||
|
||||
post-new
|
||||
This hook is invoked by the :any:`notmuch-new(1)` command after
|
||||
new messages have been imported into the database and initial tags
|
||||
have been applied. The hook will not be run if there have been any
|
||||
errors during the scan or import.
|
||||
any new messages have been imported into the database and initial
|
||||
tags have been applied. The hook will not be run if there have
|
||||
been any errors during the scan or import.
|
||||
|
||||
Typically this hook is used to perform additional query-based
|
||||
tagging on the imported messages.
|
||||
|
|
|
@ -169,6 +169,12 @@ property:<key>=<value>
|
|||
can be present on a given message with several different values.
|
||||
See :any:`notmuch-properties(7)` for more details.
|
||||
|
||||
sexp:<subquery>
|
||||
The **sexp:** prefix allows subqueries in the format
|
||||
documented in :any:`notmuch-sexp-queries(7)`. Note that subqueries containing
|
||||
spaces must be quoted, and any embedded double quotes must be escaped
|
||||
(see :any:`quoting`).
|
||||
|
||||
User defined prefixes are also supported, see :any:`notmuch-config(1)` for
|
||||
details.
|
||||
|
||||
|
@ -257,7 +263,7 @@ Boolean
|
|||
Probabilistic
|
||||
**body:**, **to:**, **attachment:**, **mimetype:**
|
||||
Special
|
||||
**from:**, **query:**, **subject:**
|
||||
**from:**, **query:**, **subject:**, **sexp:**
|
||||
|
||||
Terms and phrases
|
||||
-----------------
|
||||
|
@ -275,11 +281,13 @@ the same phrase.
|
|||
- a.list.of.words
|
||||
|
||||
Both parenthesised lists of terms and quoted phrases are ok with
|
||||
probabilistic prefixes such as **to:**, **from:**, and **subject:**. In particular
|
||||
probabilistic prefixes such as **to:**, **from:**, and **subject:**.
|
||||
For prefixes supporting regex search, the parenthesised list should be
|
||||
quoted. In particular
|
||||
|
||||
::
|
||||
|
||||
subject:(pizza free)
|
||||
subject:"(pizza free)"
|
||||
|
||||
is equivalent to
|
||||
|
||||
|
@ -295,6 +303,8 @@ Both of these will match a subject "Free Delicious Pizza" while
|
|||
|
||||
will not.
|
||||
|
||||
.. _quoting:
|
||||
|
||||
Quoting
|
||||
-------
|
||||
|
||||
|
@ -322,6 +332,13 @@ e.g.
|
|||
% notmuch search 'folder:"/^.*/(Junk|Spam)$/"'
|
||||
% notmuch search 'thread:"{from:mallory and date:2009}" and thread:{to:mallory}'
|
||||
|
||||
Double quotes within query strings need to be doubled to escape them.
|
||||
|
||||
::
|
||||
|
||||
% notmuch search 'tag:"""quoted tag"""'
|
||||
% notmuch search 'sexp:"(or ""wizard"" ""php"")"'
|
||||
|
||||
DATE AND TIME SEARCH
|
||||
====================
|
||||
|
||||
|
|
|
@ -331,11 +331,21 @@ tags.
|
|||
As is the case with :ref:`notmuch-search`, the presentation of results
|
||||
can be controlled by the variable ``notmuch-search-oldest-first``.
|
||||
|
||||
.. _notmuch-unthreaded:
|
||||
|
||||
notmuch-unthreaded
|
||||
------------------
|
||||
|
||||
``notmuch-unthreaded-mode`` is similar to :any:`notmuch-tree` in that
|
||||
each line corresponds to a single message, but no thread information
|
||||
is presented.
|
||||
|
||||
Keybindings are the same as :any:`notmuch-tree`.
|
||||
|
||||
Global key bindings
|
||||
===================
|
||||
|
||||
Several features are accessible from anywhere in notmuch through the
|
||||
Several features are accessible from most places in notmuch through the
|
||||
following key bindings:
|
||||
|
||||
``j``
|
||||
|
@ -344,6 +354,8 @@ following key bindings:
|
|||
``k``
|
||||
Tagging operations using :ref:`notmuch-tag-jump`
|
||||
|
||||
``C-_`` ``C-/`` ``C-x u``: Undo previous tagging operation using :ref:`notmuch-tag-undo`
|
||||
|
||||
.. _notmuch-jump:
|
||||
|
||||
notmuch-jump
|
||||
|
@ -373,6 +385,21 @@ operations specified in ``notmuch-tagging-keys``; i.e. each
|
|||
|
||||
|docstring::notmuch-tagging-keys|
|
||||
|
||||
.. _notmuch-tag-undo:
|
||||
|
||||
notmuch-tag-undo
|
||||
----------------
|
||||
|
||||
Each notmuch buffer supporting tagging operations (i.e buffers in
|
||||
:any:`notmuch-show`, :any:`notmuch-search`, :any:`notmuch-tree`, and
|
||||
:any:`notmuch-unthreaded` mode) keeps a local stack of tagging
|
||||
operations. These can be undone via ``notmuch-tag-undo``. By default
|
||||
this is bound to the usual Emacs keys for undo.
|
||||
|
||||
:index:`notmuch-tag-undo`
|
||||
|
||||
|docstring::notmuch-tag-undo|
|
||||
|
||||
Buffer navigation
|
||||
=================
|
||||
|
||||
|
|
|
@ -710,6 +710,9 @@ with `notmuch-hello-query-counts'."
|
|||
;; that when we modify map it does not modify widget-keymap).
|
||||
(let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap))))
|
||||
(set-keymap-parent map notmuch-common-keymap)
|
||||
;; Currently notmuch-hello-mode supports free text entry, but not
|
||||
;; tagging operations, so provide standard undo.
|
||||
(define-key map [remap notmuch-tag-undo] #'undo)
|
||||
map)
|
||||
"Keymap for \"notmuch hello\" buffers.")
|
||||
|
||||
|
|
|
@ -166,6 +166,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
|
|||
(define-key map (kbd "M-=") 'notmuch-refresh-all-buffers)
|
||||
(define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
|
||||
(define-key map "j" 'notmuch-jump-search)
|
||||
(define-key map [remap undo] 'notmuch-tag-undo)
|
||||
map)
|
||||
"Keymap shared by all notmuch modes.")
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
|
@ -275,6 +275,11 @@ This can be used with `notmuch-tag-format-image-data'."
|
|||
</g>
|
||||
</svg>")
|
||||
|
||||
;;; track history of tag operations
|
||||
(defvar-local notmuch-tag-history nil
|
||||
"Buffer local history of `notmuch-tag' function.")
|
||||
(put 'notmuch-tag-history 'permanent-local t)
|
||||
|
||||
;;; Format Handling
|
||||
|
||||
(defvar notmuch-tag--format-cache (make-hash-table :test 'equal)
|
||||
|
@ -458,14 +463,19 @@ from TAGS if present."
|
|||
"Use batch tagging if the tagging query is longer than this.
|
||||
|
||||
This limits the length of arguments passed to the notmuch CLI to
|
||||
avoid system argument length limits and performance problems.")
|
||||
avoid system argument length limits and performance problems.
|
||||
|
||||
(defun notmuch-tag (query tag-changes)
|
||||
NOTE: this variable is no longer used.")
|
||||
|
||||
(make-obsolete-variable 'notmuch-tag-argument-limit nil "notmuch 0.36")
|
||||
|
||||
(defun notmuch-tag (query tag-changes &optional omit-hist)
|
||||
"Add/remove tags in TAG-CHANGES to messages matching QUERY.
|
||||
|
||||
QUERY should be a string containing the search-terms.
|
||||
TAG-CHANGES is a list of strings of the form \"+tag\" or
|
||||
\"-tag\" to add or remove tags, respectively.
|
||||
TAG-CHANGES is a list of strings of the form \"+tag\" or \"-tag\"
|
||||
to add or remove tags, respectively. OMIT-HIST disables history
|
||||
tracking if non-nil.
|
||||
|
||||
Note: Other code should always use this function to alter tags of
|
||||
messages instead of running (notmuch-call-notmuch-process \"tag\" ..)
|
||||
|
@ -481,16 +491,30 @@ notmuch-after-tag-hook will be run."
|
|||
(notmuch-dlet ((tag-changes tag-changes)
|
||||
(query query))
|
||||
(run-hooks 'notmuch-before-tag-hook))
|
||||
(if (<= (length query) notmuch-tag-argument-limit)
|
||||
(apply 'notmuch-call-notmuch-process "tag"
|
||||
(append tag-changes (list "--" query)))
|
||||
;; Use batch tag mode to avoid argument length limitations
|
||||
(let ((batch-op (concat (mapconcat #'notmuch-hex-encode tag-changes " ")
|
||||
" -- " query)))
|
||||
(notmuch-call-notmuch-process :stdin-string batch-op "tag" "--batch")))
|
||||
(notmuch-dlet ((tag-changes tag-changes)
|
||||
(query query))
|
||||
(run-hooks 'notmuch-after-tag-hook))))
|
||||
(with-temp-buffer
|
||||
(insert (concat (mapconcat #'notmuch-hex-encode tag-changes " ") " -- " query))
|
||||
(unless (= 0
|
||||
(notmuch--call-process-region
|
||||
(point-min) (point-max) notmuch-command t t nil "tag" "--batch"))
|
||||
(notmuch-logged-error "notmuch tag failed" (buffer-string))))
|
||||
(unless omit-hist
|
||||
(push (list :query query :tag-changes tag-changes) notmuch-tag-history)))
|
||||
(notmuch-dlet ((tag-changes tag-changes)
|
||||
(query query))
|
||||
(run-hooks 'notmuch-after-tag-hook)))
|
||||
|
||||
(defun notmuch-tag-undo ()
|
||||
"Undo the previous tagging operation in the current buffer. Uses
|
||||
buffer local variable `notmuch-tag-history' to determine what
|
||||
that operation was."
|
||||
(interactive)
|
||||
(when (null notmuch-tag-history)
|
||||
(error "no further notmuch undo information"))
|
||||
(let* ((action (pop notmuch-tag-history))
|
||||
(query (plist-get action :query))
|
||||
(changes (notmuch-tag-change-list (plist-get action :tag-changes) t)))
|
||||
(notmuch-tag query changes t))
|
||||
(notmuch-refresh-this-buffer))
|
||||
|
||||
(defun notmuch-tag-change-list (tags &optional reverse)
|
||||
"Convert TAGS into a list of tag changes.
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
Supported fields are: date, count, authors, subject, tags.
|
||||
For example:
|
||||
(setq notmuch-search-result-format
|
||||
'((\"authors\" . \"%-40s\")
|
||||
\\='((\"authors\" . \"%-40s\")
|
||||
(\"subject\" . \"%s\")))
|
||||
|
||||
Line breaks are permitted in format strings (though this is
|
||||
|
|
|
@ -64,7 +64,8 @@ libnotmuch_cxx_srcs = \
|
|||
$(dir)/prefix.cc \
|
||||
$(dir)/open.cc \
|
||||
$(dir)/init.cc \
|
||||
$(dir)/parse-sexp.cc
|
||||
$(dir)/parse-sexp.cc \
|
||||
$(dir)/sexp-fp.cc
|
||||
|
||||
libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
|
||||
|
||||
|
|
|
@ -354,10 +354,6 @@ _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,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "thread-fp.h"
|
||||
#include "regexp-fields.h"
|
||||
#include "parse-time-vrp.h"
|
||||
#include "sexp-fp.h"
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
|
@ -60,6 +61,8 @@ prefix_t prefix_table[] = {
|
|||
NOTMUCH_FIELD_PROCESSOR },
|
||||
{ "query", NULL, NOTMUCH_FIELD_EXTERNAL |
|
||||
NOTMUCH_FIELD_PROCESSOR },
|
||||
{ "sexp", NULL, NOTMUCH_FIELD_EXTERNAL |
|
||||
NOTMUCH_FIELD_PROCESSOR },
|
||||
{ "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
|
||||
NOTMUCH_FIELD_PROBABILISTIC |
|
||||
NOTMUCH_FIELD_PROCESSOR },
|
||||
|
@ -138,6 +141,8 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
|
|||
fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
|
||||
else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
|
||||
fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
|
||||
else if (STRNCMP_LITERAL (prefix->name, "sexp") == 0)
|
||||
fp = (new SexpFieldProcessor (notmuch))->release ();
|
||||
else
|
||||
fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
|
||||
*notmuch->query_parser, notmuch))->release ();
|
||||
|
|
|
@ -227,6 +227,7 @@ _notmuch_query_ensure_parsed_xapian (notmuch_query_t *query)
|
|||
return NOTMUCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#if HAVE_SFSEXP
|
||||
static notmuch_status_t
|
||||
_notmuch_query_ensure_parsed_sexpr (notmuch_query_t *query)
|
||||
{
|
||||
|
@ -243,6 +244,7 @@ _notmuch_query_ensure_parsed_sexpr (notmuch_query_t *query)
|
|||
_notmuch_query_cache_terms (query);
|
||||
return NOTMUCH_STATUS_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
static notmuch_status_t
|
||||
_notmuch_query_ensure_parsed (notmuch_query_t *query)
|
||||
|
|
|
@ -227,7 +227,8 @@ RegexpFieldProcessor::operator() (const std::string & str)
|
|||
* phrase parsing, when possible */
|
||||
std::string query_str;
|
||||
|
||||
if (*str.rbegin () != '*' || str.find (' ') != std::string::npos)
|
||||
if ((str.at (0) != '(' || *str.rbegin () != ')') &&
|
||||
(*str.rbegin () != '*' || str.find (' ') != std::string::npos))
|
||||
query_str = '"' + str + '"';
|
||||
else
|
||||
query_str = str;
|
||||
|
|
44
lib/sexp-fp.cc
Normal file
44
lib/sexp-fp.cc
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* sexp-fp.cc - "sexp:" field processor glue
|
||||
*
|
||||
* This file is part of notmuch.
|
||||
*
|
||||
* Copyright © 2022 David Bremner
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/ .
|
||||
*
|
||||
* Author: David Bremner <david@tethera.net>
|
||||
*/
|
||||
|
||||
#include "database-private.h"
|
||||
#include "sexp-fp.h"
|
||||
#include <iostream>
|
||||
|
||||
Xapian::Query
|
||||
SexpFieldProcessor::operator() (const std::string & query_string)
|
||||
{
|
||||
notmuch_status_t status;
|
||||
Xapian::Query output;
|
||||
|
||||
#if HAVE_SFSEXP
|
||||
status = _notmuch_sexp_string_to_xapian_query (notmuch, query_string.c_str (), output);
|
||||
if (status) {
|
||||
throw Xapian::QueryParserError ("error parsing " + query_string);
|
||||
}
|
||||
#else
|
||||
throw Xapian::QueryParserError ("sexp query parser not available");
|
||||
#endif
|
||||
|
||||
return output;
|
||||
|
||||
}
|
41
lib/sexp-fp.h
Normal file
41
lib/sexp-fp.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* sexp-fp.h - sexp field processor glue
|
||||
*
|
||||
* This file is part of notmuch.
|
||||
*
|
||||
* Copyright © 2022 David Bremner
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/ .
|
||||
*
|
||||
* Author: David Bremner <david@tethera.net>
|
||||
*/
|
||||
|
||||
#ifndef NOTMUCH_SEXP_FP_H
|
||||
#define NOTMUCH_SEXP_FP_H
|
||||
|
||||
#include <xapian.h>
|
||||
#include "notmuch.h"
|
||||
|
||||
class SexpFieldProcessor : public Xapian::FieldProcessor {
|
||||
protected:
|
||||
notmuch_database_t *notmuch;
|
||||
|
||||
public:
|
||||
SexpFieldProcessor (notmuch_database_t *notmuch_) : notmuch (notmuch_)
|
||||
{
|
||||
};
|
||||
|
||||
Xapian::Query operator() (const std::string & query_string);
|
||||
};
|
||||
|
||||
#endif /* NOTMUCH_SEXP_FP_H */
|
|
@ -241,6 +241,26 @@ maildir_mktemp (const void *ctx, const char *maildir, bool world_readable, char
|
|||
return fd;
|
||||
}
|
||||
|
||||
static bool
|
||||
write_buf (const char *buf, int fdout, ssize_t remain)
|
||||
{
|
||||
const char *p = buf;
|
||||
|
||||
do {
|
||||
ssize_t written = write (fdout, p, remain);
|
||||
if (written < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (written <= 0) {
|
||||
fprintf (stderr, "Error: writing to temporary file: %s",
|
||||
strerror (errno));
|
||||
return false;
|
||||
}
|
||||
p += written;
|
||||
remain -= written;
|
||||
} while (remain > 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy fdin to fdout, return true on success, and false on errors and
|
||||
* empty input.
|
||||
|
@ -249,11 +269,13 @@ static bool
|
|||
copy_fd (int fdout, int fdin)
|
||||
{
|
||||
bool empty = true;
|
||||
bool first = true;
|
||||
const char *header = "X-Envelope-From: ";
|
||||
|
||||
while (! interrupted) {
|
||||
ssize_t remain;
|
||||
char buf[4096];
|
||||
char *p;
|
||||
const char *p = buf;
|
||||
|
||||
remain = read (fdin, buf, sizeof (buf));
|
||||
if (remain == 0)
|
||||
|
@ -266,20 +288,18 @@ copy_fd (int fdout, int fdin)
|
|||
return false;
|
||||
}
|
||||
|
||||
p = buf;
|
||||
do {
|
||||
ssize_t written = write (fdout, p, remain);
|
||||
if (written < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (written <= 0) {
|
||||
fprintf (stderr, "Error: writing to temporary file: %s",
|
||||
strerror (errno));
|
||||
if (first && remain >= 5 && 0 == strncmp (buf, "From ", 5)) {
|
||||
if (! write_buf (header, fdout, strlen (header)))
|
||||
return false;
|
||||
}
|
||||
p += written;
|
||||
remain -= written;
|
||||
empty = false;
|
||||
} while (remain > 0);
|
||||
p += 5;
|
||||
remain -= 5;
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
if (! write_buf (p, fdout, remain))
|
||||
return false;
|
||||
empty = false;
|
||||
}
|
||||
|
||||
return (! interrupted && ! empty);
|
||||
|
|
21
performance-test/T06-emacs.sh
Executable file
21
performance-test/T06-emacs.sh
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
test_description='emacs operations'
|
||||
|
||||
. $(dirname "$0")/perf-test-lib.sh || exit 1
|
||||
. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
|
||||
|
||||
test_require_emacs
|
||||
|
||||
time_start
|
||||
|
||||
print_emacs_header
|
||||
|
||||
MSGS=$(notmuch search --output=messages "*" | shuf -n 50 | awk '{printf " \"%s\"",$1}')
|
||||
|
||||
time_emacs "tag messages" \
|
||||
"(dolist (msg (list $MSGS))
|
||||
(notmuch-tag msg (list \"+test\"))
|
||||
(notmuch-tag msg (list \"-test\"))))"
|
||||
|
||||
time_done
|
|
@ -41,6 +41,8 @@ done
|
|||
# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
|
||||
. $(dirname "$0")/../test/export-dirs.sh || exit 1
|
||||
|
||||
. "$NOTMUCH_SRCDIR/test/test-vars.sh" || exit 1
|
||||
|
||||
# Where to run the tests
|
||||
TEST_DIRECTORY=$NOTMUCH_BUILDDIR/performance-test
|
||||
|
||||
|
@ -208,6 +210,11 @@ print_header ()
|
|||
printf "\t\t\tWall(s)\tUsr(s)\tSys(s)\tRes(K)\tIn/Out(512B)\n"
|
||||
}
|
||||
|
||||
print_emacs_header ()
|
||||
{
|
||||
printf "\t\t\tWall(s)\tGCs\tGC time(s)\n"
|
||||
}
|
||||
|
||||
time_run ()
|
||||
{
|
||||
printf " %-22s" "$1"
|
||||
|
|
|
@ -293,6 +293,26 @@ user.primary_email=test_suite@notmuchmail.org
|
|||
EOF
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "Config list from python ($config)"
|
||||
test_python <<EOF > OUTPUT
|
||||
from notmuch2 import Database
|
||||
db=Database(config=Database.CONFIG.SEARCH)
|
||||
for key in list(db.config):
|
||||
print(key)
|
||||
EOF
|
||||
cat <<EOF > EXPECTED
|
||||
database.autocommit
|
||||
database.backup_dir
|
||||
database.hook_dir
|
||||
database.mail_root
|
||||
database.path
|
||||
maildir.synchronize_flags
|
||||
new.tags
|
||||
user.name
|
||||
user.other_email
|
||||
user.primary_email
|
||||
EOF
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
case $config in
|
||||
XDG*)
|
||||
test_begin_subtest "Set shadowed config value in database ($config)"
|
||||
|
|
|
@ -292,4 +292,9 @@ for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do
|
|||
test_expect_code 0 "notmuch_with_shim shim-$code insert --keep < \"$gen_msg_filename\""
|
||||
done
|
||||
|
||||
test_begin_subtest "insert converts mboxes on delivery"
|
||||
notmuch insert +unmboxed < "${TEST_DIRECTORY}"/corpora/indexing/mbox-attachment.eml
|
||||
output=$(notmuch count tag:unmboxed)
|
||||
test_expect_equal "${output}" 1
|
||||
|
||||
test_done
|
||||
|
|
|
@ -31,6 +31,13 @@ thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea
|
|||
EOF
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "and of stemmed 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
|
||||
|
@ -39,6 +46,14 @@ thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea
|
|||
EOF
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "or of exact terms via field processor"
|
||||
notmuch search '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
|
||||
|
@ -707,6 +722,11 @@ notmuch search property:foo=bar > EXPECTED
|
|||
notmuch search --query=sexp '(property (rx foo=.*))' > OUTPUT
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "regexp 'property' search via field processor"
|
||||
notmuch search property:foo=bar > EXPECTED
|
||||
notmuch search '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
|
||||
|
@ -743,6 +763,13 @@ thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packa
|
|||
EOF
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "Compound subquery via field processor"
|
||||
notmuch search '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
|
||||
|
@ -969,6 +996,11 @@ 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 via field processor"
|
||||
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
|
||||
notmuch search --output=files '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
|
||||
|
|
|
@ -435,7 +435,7 @@ test_expect_equal_file EXPECTED OUTPUT
|
|||
|
||||
test_begin_subtest "search for non-existent message prints nothing"
|
||||
notmuch search "no-message-matches-this" > OUTPUT
|
||||
echo -n >EXPECTED
|
||||
: >EXPECTED
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "search --format=json for non-existent message prints proper empty json"
|
||||
|
|
|
@ -683,7 +683,7 @@ test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
|
|||
|
||||
test_begin_subtest "'notmuch show --part' does not corrupt a part with CRLF pair"
|
||||
notmuch show --format=raw --part=3 id:base64-part-with-crlf > crlf.out
|
||||
echo -n -e "\xEF\x0D\x0A" > crlf.expected
|
||||
printf "\xEF\x0D\x0A" > crlf.expected
|
||||
test_expect_equal_file crlf.out crlf.expected
|
||||
|
||||
|
||||
|
|
|
@ -130,75 +130,6 @@ test_emacs '(notmuch-search "tag:inbox")
|
|||
(test-output)'
|
||||
test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage OUTPUT
|
||||
|
||||
test_begin_subtest "Add tag from search view"
|
||||
os_x_darwin_thread=$(notmuch search --output=threads id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com)
|
||||
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+tag-from-search-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag-from-search-view unread)"
|
||||
|
||||
test_begin_subtest "Remove tag from search view"
|
||||
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"-tag-from-search-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 "Add tag (large query)"
|
||||
# We use a long query to force us into batch mode and use a funny tag
|
||||
# that requires escaping for batch tagging.
|
||||
test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (mapconcat #'identity (make-list notmuch-tag-argument-limit \"x\") \"-\")) (list \"+tag-from-%-large-query\"))"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag-from-%-large-query unread)"
|
||||
notmuch tag -tag-from-%-large-query $os_x_darwin_thread
|
||||
|
||||
test_begin_subtest "notmuch-show: add single tag to single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"+tag-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag-from-show-view unread)"
|
||||
|
||||
test_begin_subtest "notmuch-show: remove single tag from single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"-tag-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 "notmuch-show: add multiple tags to single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"+tag1-from-show-view +tag2-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag1-from-show-view tag2-from-show-view unread)"
|
||||
|
||||
test_begin_subtest "notmuch-show: remove multiple tags from single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"-tag1-from-show-view -tag2-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 "notmuch-show: before-tag-hook is run, variables are defined"
|
||||
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
|
||||
(notmuch-before-tag-hook (function notmuch-test-tag-hook)))
|
||||
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
|
||||
(execute-kbd-macro "+activate-hook\n")
|
||||
(execute-kbd-macro "-activate-hook\n")
|
||||
notmuch-test-tag-hook-output)')
|
||||
test_expect_equal "$output" \
|
||||
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
|
||||
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
|
||||
|
||||
test_begin_subtest "notmuch-show: after-tag-hook is run, variables are defined"
|
||||
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
|
||||
(notmuch-after-tag-hook (function notmuch-test-tag-hook)))
|
||||
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
|
||||
(execute-kbd-macro "+activate-hook\n")
|
||||
(execute-kbd-macro "-activate-hook\n")
|
||||
notmuch-test-tag-hook-output)')
|
||||
test_expect_equal "$output" \
|
||||
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
|
||||
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
|
||||
|
||||
test_begin_subtest "Message with .. in Message-Id:"
|
||||
add_message [id]=123..456@example '[subject]="Message with .. in Message-Id"'
|
||||
test_emacs '(notmuch-search "id:\"123..456@example\"")
|
||||
|
@ -1133,30 +1064,6 @@ This is a warning (see *Notmuch errors* for more details)
|
|||
This is a warning
|
||||
This is another warning"
|
||||
|
||||
test_begin_subtest "Search thread tag operations are race-free"
|
||||
add_message '[subject]="Search race test"'
|
||||
gen_msg_id_1=$gen_msg_id
|
||||
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
|
||||
'[references]="<'$gen_msg_id_1'>"' \
|
||||
'[subject]="Search race test two"'
|
||||
test_emacs '(notmuch-search "subject:\"search race test\"")
|
||||
(notmuch-test-wait)
|
||||
(notmuch-poll)
|
||||
(execute-kbd-macro "+search-thread-race-tag")'
|
||||
output=$(notmuch search --output=messages 'tag:search-thread-race-tag')
|
||||
test_expect_equal "$output" "id:$gen_msg_id_1"
|
||||
|
||||
test_begin_subtest "Search global tag operations are race-free"
|
||||
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
|
||||
'[references]="<'$gen_msg_id_1'>"' \
|
||||
'[subject]="Re: Search race test"'
|
||||
test_emacs '(notmuch-search "subject:\"search race test\" -subject:two")
|
||||
(notmuch-test-wait)
|
||||
(notmuch-poll)
|
||||
(execute-kbd-macro "*+search-global-race-tag")'
|
||||
output=$(notmuch search --output=messages 'tag:search-global-race-tag')
|
||||
test_expect_equal "$output" "id:$gen_msg_id_1"
|
||||
|
||||
test_begin_subtest "Term escaping"
|
||||
output=$(test_emacs "(mapcar 'notmuch-escape-boolean-term (list
|
||||
\"\"
|
||||
|
|
166
test/T315-emacs-tagging.sh
Executable file
166
test/T315-emacs-tagging.sh
Executable file
|
@ -0,0 +1,166 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
test_description="emacs interface"
|
||||
. $(dirname "$0")/test-lib.sh || exit 1
|
||||
. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
|
||||
|
||||
EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
|
||||
|
||||
test_require_emacs
|
||||
add_email_corpus
|
||||
|
||||
test_begin_subtest "Add tag from search view"
|
||||
os_x_darwin_thread=$(notmuch search --output=threads id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com)
|
||||
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+tag-from-search-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag-from-search-view unread)"
|
||||
|
||||
test_begin_subtest "Remove tag from search view"
|
||||
test_emacs "(notmuch-search \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"-tag-from-search-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 "Add tag (large query)"
|
||||
# We use a long query to force us into batch mode and use a funny tag
|
||||
# that requires escaping for batch tagging.
|
||||
test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (mapconcat #'identity (make-list notmuch-tag-argument-limit \"x\") \"-\")) (list \"+tag-from-%-large-query\"))"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag-from-%-large-query unread)"
|
||||
notmuch tag -tag-from-%-large-query $os_x_darwin_thread
|
||||
|
||||
test_begin_subtest "notmuch-show: add single tag to single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"+tag-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag-from-show-view unread)"
|
||||
|
||||
test_begin_subtest "notmuch-show: remove single tag from single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"-tag-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 "notmuch-show: add multiple tags to single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"+tag1-from-show-view +tag2-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 tag1-from-show-view tag2-from-show-view unread)"
|
||||
|
||||
test_begin_subtest "notmuch-show: remove multiple tags from single message"
|
||||
test_emacs "(notmuch-show \"$os_x_darwin_thread\")
|
||||
(execute-kbd-macro \"-tag1-from-show-view -tag2-from-show-view\")"
|
||||
output=$(notmuch search $os_x_darwin_thread | 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 "notmuch-show: before-tag-hook is run, variables are defined"
|
||||
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
|
||||
(notmuch-before-tag-hook (function notmuch-test-tag-hook)))
|
||||
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
|
||||
(execute-kbd-macro "+activate-hook\n")
|
||||
(execute-kbd-macro "-activate-hook\n")
|
||||
notmuch-test-tag-hook-output)')
|
||||
test_expect_equal "$output" \
|
||||
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
|
||||
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
|
||||
|
||||
test_begin_subtest "notmuch-show: after-tag-hook is run, variables are defined"
|
||||
output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
|
||||
(notmuch-after-tag-hook (function notmuch-test-tag-hook)))
|
||||
(notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
|
||||
(execute-kbd-macro "+activate-hook\n")
|
||||
(execute-kbd-macro "-activate-hook\n")
|
||||
notmuch-test-tag-hook-output)')
|
||||
test_expect_equal "$output" \
|
||||
'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
|
||||
("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
|
||||
|
||||
|
||||
test_begin_subtest "Search thread tag operations are race-free"
|
||||
add_message '[subject]="Search race test"'
|
||||
gen_msg_id_1=$gen_msg_id
|
||||
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
|
||||
'[references]="<'$gen_msg_id_1'>"' \
|
||||
'[subject]="Search race test two"'
|
||||
test_emacs '(notmuch-search "subject:\"search race test\"")
|
||||
(notmuch-test-wait)
|
||||
(notmuch-poll)
|
||||
(execute-kbd-macro "+search-thread-race-tag")'
|
||||
output=$(notmuch search --output=messages 'tag:search-thread-race-tag')
|
||||
test_expect_equal "$output" "id:$gen_msg_id_1"
|
||||
|
||||
test_begin_subtest "Search global tag operations are race-free"
|
||||
generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
|
||||
'[references]="<'$gen_msg_id_1'>"' \
|
||||
'[subject]="Re: Search race test"'
|
||||
test_emacs '(notmuch-search "subject:\"search race test\" -subject:two")
|
||||
(notmuch-test-wait)
|
||||
(notmuch-poll)
|
||||
(execute-kbd-macro "*+search-global-race-tag")'
|
||||
output=$(notmuch search --output=messages 'tag:search-global-race-tag')
|
||||
test_expect_equal "$output" "id:$gen_msg_id_1"
|
||||
|
||||
test_begin_subtest "undo with empty history is an error"
|
||||
test_emacs "(let ((notmuch-tag-history nil))
|
||||
(test-log-error
|
||||
(notmuch-tag-undo)))
|
||||
"
|
||||
cat <<EOF > EXPECTED
|
||||
(error no further notmuch undo information)
|
||||
EOF
|
||||
test_expect_equal_file EXPECTED MESSAGES
|
||||
|
||||
for mode in search show tree unthreaded; do
|
||||
test_begin_subtest "undo tagging in $mode mode"
|
||||
test_emacs "(let ((notmuch-tag-history nil))
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
|
||||
(notmuch-tag-undo)
|
||||
(notmuch-test-wait))"
|
||||
count=$(notmuch count "tag:tag-to-be-undone-$mode")
|
||||
test_expect_equal "$count" "0"
|
||||
|
||||
test_begin_subtest "undo tagging in $mode mode (multiple operations)"
|
||||
test_emacs "(let ((notmuch-tag-history nil))
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+one-$mode\")
|
||||
(execute-kbd-macro \"+two-$mode\")
|
||||
(notmuch-tag-undo)
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+three-$mode\"))"
|
||||
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
|
||||
notmuch tag "-one-$mode" "-three-$mode" $os_x_darwin_thread
|
||||
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox one-$mode three-$mode unread)"
|
||||
|
||||
test_begin_subtest "undo tagging in $mode mode (multiple undo)"
|
||||
test_emacs "(let ((notmuch-tag-history nil))
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+one-$mode\")
|
||||
(execute-kbd-macro \"+two-$mode\")
|
||||
(notmuch-tag-undo)
|
||||
(notmuch-test-wait)
|
||||
(notmuch-tag-undo)
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+three-$mode\"))"
|
||||
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
|
||||
notmuch tag "-one-$mode" "-three-$mode" $os_x_darwin_thread
|
||||
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox three-$mode unread)"
|
||||
|
||||
test_begin_subtest "undo tagging in $mode mode (via binding)"
|
||||
test_emacs "(let ((notmuch-tag-history nil))
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
|
||||
(execute-kbd-macro (kbd \"C-x u\"))
|
||||
(notmuch-test-wait))"
|
||||
count=$(notmuch count "tag:tag-to-be-undone-$mode")
|
||||
test_expect_equal "$count" "0"
|
||||
done
|
||||
|
||||
test_done
|
|
@ -35,6 +35,11 @@ EOF
|
|||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "signature verification (notmuch CLI)"
|
||||
if [ $NOTMUCH_GMIME_EMITS_ANGLE_BRACKETS == 1 ]; then
|
||||
EXPECTED_EMAIL_ADDR='<test_suite@notmuchmail.org>'
|
||||
else
|
||||
EXPECTED_EMAIL_ADDR='test_suite@notmuchmail.org'
|
||||
fi
|
||||
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
|
||||
| notmuch_json_show_sanitize \
|
||||
| sed -e 's|"created": [-1234567890]*|"created": 946728000|g' \
|
||||
|
@ -46,7 +51,7 @@ expected='[[[{"id": "XXXXX",
|
|||
"timestamp": 946728000,
|
||||
"date_relative": "2000-01-01",
|
||||
"tags": ["inbox","signed"],
|
||||
"crypto": {"signed": {"status": [{"fingerprint": "'$FINGERPRINT'", "status": "good","userid": "CN=Notmuch Test Suite", "email": "<test_suite@notmuchmail.org>", "expires": 424242424, "created": 946728000}]}},
|
||||
"crypto": {"signed": {"status": [{"fingerprint": "'$FINGERPRINT'", "status": "good","userid": "CN=Notmuch Test Suite", "email": "'$EXPECTED_EMAIL_ADDR'", "expires": 424242424, "created": 946728000}]}},
|
||||
"headers": {"Subject": "test signed message 001",
|
||||
"From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
|
||||
"To": "test_suite@notmuchmail.org",
|
||||
|
@ -55,7 +60,7 @@ expected='[[[{"id": "XXXXX",
|
|||
"sigstatus": [{"fingerprint": "'$FINGERPRINT'",
|
||||
"status": "good",
|
||||
"userid": "CN=Notmuch Test Suite",
|
||||
"email": "<test_suite@notmuchmail.org>",
|
||||
"email": "'$EXPECTED_EMAIL_ADDR'",
|
||||
"expires": 424242424,
|
||||
"created": 946728000}],
|
||||
"content-type": "multipart/signed",
|
||||
|
|
|
@ -71,8 +71,8 @@ if test_require_external_prereq gdb; then
|
|||
|
||||
# Check output against golden output
|
||||
outcount=$(cat outcount)
|
||||
echo -n > searchall
|
||||
echo -n > expectall
|
||||
: > searchall
|
||||
: > expectall
|
||||
for ((i = 0; i < $outcount; i++)); do
|
||||
if ! cmp -s search.$i expected; then
|
||||
# Find the range of interruptions that match this output
|
||||
|
|
|
@ -65,6 +65,31 @@ thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; - (inbox unread)
|
|||
EOF
|
||||
test_expect_equal_file EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "bracketed subject search (with dquotes)"
|
||||
notmuch search subject:notmuch and subject:show > EXPECTED
|
||||
notmuch search 'subject:"(show notmuch)"' > OUTPUT
|
||||
test_expect_equal_file_nonempty EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "bracketed subject search (with dquotes and operator 'or')"
|
||||
notmuch search subject:notmuch or subject:show > EXPECTED
|
||||
notmuch search 'subject:"(notmuch or show)"' > OUTPUT
|
||||
test_expect_equal_file_nonempty EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "bracketed subject search (with dquotes and operator 'and')"
|
||||
notmuch search subject:notmuch and subject:show > EXPECTED
|
||||
notmuch search 'subject:"(notmuch and show)"' > OUTPUT
|
||||
test_expect_equal_file_nonempty EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "bracketed subject search (with phrase, operator 'or')"
|
||||
notmuch search 'subject:"mailing list"' or subject:FreeBSD > EXPECTED
|
||||
notmuch search 'subject:"(""mailing list"" or FreeBSD)"' > OUTPUT
|
||||
test_expect_equal_file_nonempty EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "bracketed subject search (with phrase, operator 'and')"
|
||||
notmuch search search 'subject:"notmuch show"' and subject:commands > EXPECTED
|
||||
notmuch search 'subject:"(""notmuch show"" and commands)"' > OUTPUT
|
||||
test_expect_equal_file_nonempty EXPECTED OUTPUT
|
||||
|
||||
test_begin_subtest "xapian wildcard search for from:"
|
||||
notmuch search --output=messages 'from:cwo*' > OUTPUT
|
||||
test_expect_equal_file cworth.msg-ids OUTPUT
|
||||
|
|
83
test/corpora/indexing/mbox-attachment.eml
Normal file
83
test/corpora/indexing/mbox-attachment.eml
Normal file
|
@ -0,0 +1,83 @@
|
|||
From david@tethera.net Sat Feb 5 09:19:10 2022
|
||||
From: David Bremner <david@tethera.net>
|
||||
To: David Bremner <david@tethera.net>
|
||||
Subject: Re: [RFC PATCH v2 12/12] emacs: whitespace cleanup for keybindings
|
||||
Date: Sat, 05 Feb 2022 10:19:09 -0400
|
||||
Message-ID: <87k0e9o0pu.fsf@tethera.net>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/mixed; boundary="=-=-="
|
||||
|
||||
--=-=-=
|
||||
Content-Type: text/plain
|
||||
Content-Disposition: inline
|
||||
|
||||
|
||||
I figured out the race condition in the tests. The previous test was
|
||||
still running when the failing test started, the joys of using a shared
|
||||
emacs for running all of the tests in one file.
|
||||
|
||||
The attached diff is split into the the commits that introduce the tests
|
||||
in question in my working series, but you should be able to just apply
|
||||
it on top of the posted series if you want.
|
||||
|
||||
|
||||
--=-=-=
|
||||
Content-Type: text/x-diff
|
||||
Content-Disposition: inline; filename=0001-test-fixups.patch
|
||||
|
||||
From fc88cba7f1f37b9cf3b296eace2422dd0e173502 Mon Sep 17 00:00:00 2001
|
||||
From: David Bremner <david@tethera.net>
|
||||
Date: Thu, 3 Feb 2022 21:05:05 -0400
|
||||
Subject: [PATCH] test fixups
|
||||
|
||||
---
|
||||
test/T315-emacs-tagging.sh | 9 ++++-----
|
||||
1 file changed, 4 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/test/T315-emacs-tagging.sh b/test/T315-emacs-tagging.sh
|
||||
index c9e3e53a..c26413ce 100755
|
||||
--- a/test/T315-emacs-tagging.sh
|
||||
+++ b/test/T315-emacs-tagging.sh
|
||||
@@ -119,7 +119,8 @@ for mode in search show tree unthreaded; do
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
|
||||
- (notmuch-tag-undo))"
|
||||
+ (notmuch-tag-undo)
|
||||
+ (notmuch-test-wait))"
|
||||
count=$(notmuch count "tag:tag-to-be-undone-$mode")
|
||||
test_expect_equal "$count" "0"
|
||||
|
||||
@@ -128,9 +129,7 @@ for mode in search show tree unthreaded; do
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+one-$mode\")
|
||||
- (notmuch-test-wait)
|
||||
(execute-kbd-macro \"+two-$mode\")
|
||||
- (notmuch-test-wait)
|
||||
(notmuch-tag-undo)
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+three-$mode\"))"
|
||||
@@ -143,7 +142,6 @@ for mode in search show tree unthreaded; do
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+one-$mode\")
|
||||
- (notmuch-test-wait)
|
||||
(execute-kbd-macro \"+two-$mode\")
|
||||
(notmuch-tag-undo)
|
||||
(notmuch-test-wait)
|
||||
@@ -159,7 +157,8 @@ for mode in search show tree unthreaded; do
|
||||
(notmuch-$mode \"$os_x_darwin_thread\")
|
||||
(notmuch-test-wait)
|
||||
(execute-kbd-macro \"+tag-to-be-undone-$mode\")
|
||||
- (execute-kbd-macro (kbd \"C-x u\")))"
|
||||
+ (execute-kbd-macro (kbd \"C-x u\"))
|
||||
+ (notmuch-test-wait))"
|
||||
count=$(notmuch count "tag:tag-to-be-undone-$mode")
|
||||
test_expect_equal "$count" "0"
|
||||
done
|
||||
--
|
||||
2.30.2
|
||||
|
||||
|
||||
--=-=-=--
|
|
@ -29,6 +29,20 @@ if [[ -z "$NOTMUCH_SRCDIR" ]] || [[ -z "$NOTMUCH_BUILDDIR" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Explicitly require external prerequisite. Useful when binary is
|
||||
# called indirectly (e.g. from emacs).
|
||||
# Returns success if dependency is available, failure otherwise.
|
||||
test_require_external_prereq () {
|
||||
local binary
|
||||
binary="$1"
|
||||
if [[ ${test_missing_external_prereq_["${binary}"]} == t ]]; then
|
||||
# dependency is missing, call the replacement function to note it
|
||||
eval "$binary"
|
||||
else
|
||||
true
|
||||
fi
|
||||
}
|
||||
|
||||
backup_database () {
|
||||
test_name=$(basename $0 .sh)
|
||||
rm -rf $TMP_DIRECTORY/notmuch-dir-backup."$test_name"
|
||||
|
|
|
@ -207,4 +207,12 @@ test_emacs () {
|
|||
${TEST_EMACSCLIENT} --socket-name="$EMACS_SERVER" --eval "(notmuch-test-progn $*)"
|
||||
}
|
||||
|
||||
time_emacs () {
|
||||
rm -f MESSAGES
|
||||
printf "%s" "$1"
|
||||
shift
|
||||
test_emacs "(test-time $*)" > emacs.out
|
||||
tail -n 1 MESSAGES
|
||||
}
|
||||
|
||||
emacs_generate_script
|
||||
|
|
|
@ -186,6 +186,11 @@ running, quit if it terminated."
|
|||
(t (message "%s" err)))
|
||||
(with-current-buffer "*Messages*" (test-output "MESSAGES"))))
|
||||
|
||||
(defmacro test-time (&rest body)
|
||||
`(let ((results (mapcar (lambda (x) (/ x 5.0)) (benchmark-run 5 ,@body))))
|
||||
(message "\t\t%0.2f\t%0.2f\t%0.2f" (nth 0 results) (nth 1 results) (nth 2 results))
|
||||
(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
|
||||
|
|
|
@ -64,55 +64,7 @@ exec 6>&1 7>&2
|
|||
BASH_XTRACEFD=7
|
||||
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||
|
||||
# Keep the original TERM for say_color and test_emacs
|
||||
ORIGINAL_TERM=$TERM
|
||||
|
||||
# Set SMART_TERM to vt100 for known dumb/unknown terminal.
|
||||
# Otherwise use whatever TERM is currently used so that
|
||||
# users' actual TERM environments are being used in tests.
|
||||
case ${TERM-} in
|
||||
'' | dumb | unknown )
|
||||
SMART_TERM=vt100 ;;
|
||||
*)
|
||||
SMART_TERM=$TERM ;;
|
||||
esac
|
||||
|
||||
# For repeatability, reset the environment to known value.
|
||||
LANG=C
|
||||
LC_ALL=C
|
||||
PAGER=cat
|
||||
TZ=UTC
|
||||
TERM=dumb
|
||||
export LANG LC_ALL PAGER TERM TZ
|
||||
GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
|
||||
if [[ ( -n "$TEST_EMACS" && -z "$TEST_EMACSCLIENT" ) || \
|
||||
( -z "$TEST_EMACS" && -n "$TEST_EMACSCLIENT" ) ]]; then
|
||||
echo "error: must specify both or neither of TEST_EMACS and TEST_EMACSCLIENT" >&2
|
||||
exit 1
|
||||
fi
|
||||
TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}}
|
||||
TEST_EMACSCLIENT=${TEST_EMACSCLIENT:-emacsclient}
|
||||
TEST_GDB=${TEST_GDB:-gdb}
|
||||
TEST_CC=${TEST_CC:-cc}
|
||||
TEST_CFLAGS=${TEST_CFLAGS:-"-g -O0"}
|
||||
TEST_SHIM_CFLAGS=${TEST_SHIM_CFLAGS:-"-fpic -shared"}
|
||||
TEST_SHIM_LDFLAGS=${TEST_SHIM_LDFLAGS:-"-ldl"}
|
||||
|
||||
# Protect ourselves from common misconfiguration to export
|
||||
# CDPATH into the environment
|
||||
unset CDPATH
|
||||
|
||||
unset GREP_OPTIONS
|
||||
|
||||
# For lib/open.cc:_load_key_file
|
||||
unset XDG_CONFIG_HOME
|
||||
|
||||
# For emacsclient
|
||||
unset ALTERNATE_EDITOR
|
||||
|
||||
# for reproducibility
|
||||
unset EMAIL
|
||||
unset NAME
|
||||
. "$NOTMUCH_SRCDIR/test/test-vars.sh" || exit 1
|
||||
|
||||
add_gnupg_home () {
|
||||
[ -e "${GNUPGHOME}/gpg.conf" ] && return
|
||||
|
@ -330,11 +282,6 @@ die () {
|
|||
exit 1
|
||||
}
|
||||
|
||||
GIT_EXIT_OK=
|
||||
# Note: TEST_TMPDIR *NOT* exported!
|
||||
TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
|
||||
# Put GNUPGHOME in TMPDIR to avoid problems with long paths.
|
||||
export GNUPGHOME="${TEST_TMPDIR}/gnupg"
|
||||
trap 'trap_exit' EXIT
|
||||
trap 'trap_signal' HUP INT TERM
|
||||
|
||||
|
@ -656,20 +603,6 @@ $binary () {
|
|||
fi
|
||||
}
|
||||
|
||||
# Explicitly require external prerequisite. Useful when binary is
|
||||
# called indirectly (e.g. from emacs).
|
||||
# Returns success if dependency is available, failure otherwise.
|
||||
test_require_external_prereq () {
|
||||
local binary
|
||||
binary="$1"
|
||||
if [[ ${test_missing_external_prereq_["${binary}"]} == t ]]; then
|
||||
# dependency is missing, call the replacement function to note it
|
||||
eval "$binary"
|
||||
else
|
||||
true
|
||||
fi
|
||||
}
|
||||
|
||||
# You are not expected to call test_ok_ and test_failure_ directly, use
|
||||
# the text_expect_* functions instead.
|
||||
|
||||
|
|
62
test/test-vars.sh
Normal file
62
test/test-vars.sh
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Common variable settings for (correctness) tests and performance
|
||||
# tests.
|
||||
|
||||
# Keep the original TERM for say_color and test_emacs
|
||||
ORIGINAL_TERM=$TERM
|
||||
|
||||
# Set SMART_TERM to vt100 for known dumb/unknown terminal.
|
||||
# Otherwise use whatever TERM is currently used so that
|
||||
# users' actual TERM environments are being used in tests.
|
||||
case ${TERM-} in
|
||||
'' | dumb | unknown )
|
||||
SMART_TERM=vt100 ;;
|
||||
*)
|
||||
SMART_TERM=$TERM ;;
|
||||
esac
|
||||
|
||||
# For repeatability, reset the environment to known value.
|
||||
LANG=C
|
||||
LC_ALL=C
|
||||
PAGER=cat
|
||||
TZ=UTC
|
||||
TERM=dumb
|
||||
export LANG LC_ALL PAGER TERM TZ
|
||||
GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
|
||||
if [[ ( -n "$TEST_EMACS" && -z "$TEST_EMACSCLIENT" ) || \
|
||||
( -z "$TEST_EMACS" && -n "$TEST_EMACSCLIENT" ) ]]; then
|
||||
echo "error: must specify both or neither of TEST_EMACS and TEST_EMACSCLIENT" >&2
|
||||
exit 1
|
||||
fi
|
||||
TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}}
|
||||
TEST_EMACSCLIENT=${TEST_EMACSCLIENT:-emacsclient}
|
||||
TEST_GDB=${TEST_GDB:-gdb}
|
||||
TEST_CC=${TEST_CC:-cc}
|
||||
TEST_CFLAGS=${TEST_CFLAGS:-"-g -O0"}
|
||||
TEST_SHIM_CFLAGS=${TEST_SHIM_CFLAGS:-"-fpic -shared"}
|
||||
TEST_SHIM_LDFLAGS=${TEST_SHIM_LDFLAGS:-"-ldl"}
|
||||
|
||||
# Protect ourselves from common misconfiguration to export
|
||||
# CDPATH into the environment
|
||||
unset CDPATH
|
||||
|
||||
unset GREP_OPTIONS
|
||||
|
||||
# For lib/open.cc:_load_key_file
|
||||
unset XDG_CONFIG_HOME
|
||||
|
||||
# for lib/open.cc:_choose_database_path
|
||||
unset XDG_DATA_HOME
|
||||
unset MAILDIR
|
||||
|
||||
# For emacsclient
|
||||
unset ALTERNATE_EDITOR
|
||||
|
||||
# for reproducibility
|
||||
unset EMAIL
|
||||
unset NAME
|
||||
|
||||
GIT_EXIT_OK=
|
||||
# Note: TEST_TMPDIR *NOT* exported!
|
||||
TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
|
||||
# Put GNUPGHOME in TMPDIR to avoid problems with long paths.
|
||||
export GNUPGHOME="${TEST_TMPDIR}/gnupg"
|
|
@ -1 +1 @@
|
|||
0.35
|
||||
0.36
|
||||
|
|
Loading…
Add table
Reference in a new issue