Re: [PATCH] notmuch(1): clarify documentation about --option/value separators
Carl Worth writes: > On Thu, May 07 2020, Daniel Kahn Gillmor wrote: >> +separator. Except for boolean options (wihch would be ambiguous), a > > Misspelling of "which". And while I'm here, strictly speaking Boolean is > generally capitalized in English, (being one of those adjectives that is > derived from a proper noun). I pushed this to master with s/wihch/which/. I left boolean for a future patch since there are around 7 occurences in the docs, currently all lower case. d ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] notmuch(1): clarify documentation about --option/value separators
On Thu 2020-05-07 16:40:26 -0700, Carl Worth wrote: > On Thu, May 07 2020, Daniel Kahn Gillmor wrote: >> +separator. Except for boolean options (wihch would be ambiguous), a > > Misspelling of "which". And while I'm here, strictly speaking Boolean is > generally capitalized in English, (being one of those adjectives that is > derived from a proper noun). > > Otherwise, this looks like a good improvement to me. Thanks! Thanks for catching the "which" misspelling, which should obviously be fixed if/when the patch is applied. notmuch-search-terms(7) contains both capitalized and non-capitalized spellings of "boolean". I welcome capitalization cleanup for this over the whole codebase, and hope that a decision about this particular nit-pick isn't cause for delay of an otherwise uncontroversial improvement. --dkg signature.asc Description: PGP signature ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH 1/2] test-lib: mark function variables as local
On Thu 2020-05-07 10:31:38 +0300, Tomi Ollila wrote: > Good stuff > > robustness comment IMO: > > There is slight difference when writing > > local foo=`false` > > and > > local foo; foo=`false` > > > former does not "fail"; latter does, thanks for pointing this out. On IRC, jindraj pointed me to http://tldp.org/LDP/abs/html/localvar.html this is an unusual and confusing set of behaviors i had not understood about how local interacts with return codes, etc. I'll send around a revised set of patches that uses "local foo" instead of "local foo=". --dkg signature.asc Description: PGP signature ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
[PATCH 1/2 v2] test-lib: mark function variables as local
Several functions in test/test-lib.sh used variable names that are also used outside of those functions (e.g. $output and $expected are used in many of the test scripts), but they are not expected to communicate via those variables. We mark those variables "local" within test-lib.sh so that they do not get clobbered when used outside test-lib. We also move the local variable declarations to beginning of each function, to avoid weird gotchas with local variable declarations as described in https://tldp.org/LDP/abs/html/localvar.html. Signed-off-by: Daniel Kahn Gillmor --- test/test-lib.sh | 31 ++- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test/test-lib.sh b/test/test-lib.sh index 5c8eab7c..58972339 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -109,7 +109,6 @@ unset ALTERNATE_EDITOR add_gnupg_home () { -local output [ -e "${GNUPGHOME}/gpg.conf" ] && return _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; } at_exit_function _gnupg_exit @@ -345,13 +344,14 @@ trap 'trap_signal' HUP INT TERM # to the message and encrypting/signing. emacs_deliver_message () { -local subject="$1" -local body="$2" +local subject body smtp_dummy_pid smtp_dummy_port +subject="$1" +body="$2" shift 2 # before we can send a message, we have to prepare the FCC maildir mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp} # eval'ing smtp-dummy --background will set smtp_dummy_pid and -_port -local smtp_dummy_pid= smtp_dummy_port= +smtp_dummy_pid= smtp_dummy_port= eval `$TEST_DIRECTORY/smtp-dummy --background sent_message` test -n "$smtp_dummy_pid" || return 1 test -n "$smtp_dummy_port" || return 1 @@ -391,13 +391,14 @@ emacs_deliver_message () # new" after message delivery emacs_fcc_message () { -local nmn_args='' +local nmn_args subject body +nmn_args='' while [[ "$1" =~ ^-- ]]; do nmn_args="$nmn_args $1" shift done -local subject="$1" -local body="$2" +subject="$1" +body="$2" shift 2 # before we can send a message, we have to prepare the FCC maildir mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp} @@ -427,6 +428,7 @@ emacs_fcc_message () # number of messages. add_email_corpus () { +local corpus corpus=${1:-default} rm -rf ${MAIL_DIR} @@ -457,6 +459,7 @@ test_begin_subtest () # name. test_expect_equal () { + local output expected testname exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_expect_equal without test_begin_subtest" @@ -483,6 +486,7 @@ test_expect_equal () # Like test_expect_equal, but takes two filenames. test_expect_equal_file () { + local file1 file2 testname basename1 basename2 exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_expect_equal_file without test_begin_subtest" @@ -512,10 +516,11 @@ test_expect_equal_file () # canonicalized before diff'ing. If an argument cannot be parsed, it # is used unchanged so that there's something to diff against. test_expect_equal_json () { +local script output expected # The test suite forces LC_ALL=C, but this causes Python 3 to # decode stdin as ASCII. We need to read JSON in UTF-8, so # override Python's stdio encoding defaults. -local script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)' +script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)' output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \ || echo "$1") expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \ @@ -540,6 +545,7 @@ test_sort_json () { # read the source of test/json_check_nodes.py (or the output when # invoking it without arguments) for an explanation of the syntax. test_json_nodes () { +local output exec 1>&6 2>&7 # Restore stdout and stderr if [ -z "$inside_subtest" ]; then error "bug in the test script: test_json_eval without test_begin_subtest" @@ -561,6 +567,7 @@ test_json_nodes () { } test_emacs_expect_t () { + local result test "$#" = 1 || error "bug in the test script: not 1 parameter to test_emacs_expect_t" if [ -z "$inside_subtest" ]; then @@ -653,7 +660,8 @@ notmuch_json_show_sanitize () notmuch_emacs_error_sanitize () { -local command=$1 +local command +command=$1 shift for file in "$@"; do echo "=== $file ===" @@ -717,6 +725,7 @@ declare -A test_subtest_missing_external_prereq_ # declare prerequisite for the given external binary test_declare_external_prereq () { + local binary binary="$1" test "$#" = 2 && name=$2 || name="$bin
[PATCH 1/2] python/notmuch2: do not destroy messages owned by a query
Any messages retrieved from a query - either directly via search_messages() or indirectly via thread objects - are owned by that query. Retrieving the same message (i.e. corresponding to the same message ID / database object) several times will always yield the same C object. The caller is allowed to destroy message objects owned by a query before the query itself - which can save memory for long-lived queries. However, that message must then never be retrieved again from that query. The python-notmuch2 bindings will currently destroy every message object in Message._destroy(), which will lead to an invalid free if the same message is then retrieved again. E.g. the following python program leads to libtalloc abort()ing: import notmuch2 db = notmuch2.Database(mode = notmuch2.Database.MODE.READ_ONLY) t= next(db.threads('*')) msgs = list(zip(t.toplevel(), t.toplevel())) msgs = list(zip(t.toplevel(), t.toplevel())) Fix this issue by creating a subclass of Message, which is used for "standalone" message which have to be freed by the caller. Message class is then used only for messages descended from a query, which do not need to be freed by the caller. --- bindings/python-cffi/notmuch2/_database.py | 6 ++-- bindings/python-cffi/notmuch2/_message.py | 42 ++ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index 95f59ca0..f14eac78 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -399,7 +399,7 @@ class Database(base.NotmuchObject): capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] if ret not in ok: raise errors.NotmuchError(ret) -msg = message.Message(self, msg_pp[0], db=self) +msg = message.StandaloneMessage(self, msg_pp[0], db=self) if sync_flags: msg.tags.from_maildir_flags() return self.AddedMessage( @@ -468,7 +468,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError -msg = message.Message(self, msg_p, db=self) +msg = message.StandaloneMessage(self, msg_p, db=self) return msg def get(self, filename): @@ -501,7 +501,7 @@ class Database(base.NotmuchObject): msg_p = msg_pp[0] if msg_p == capi.ffi.NULL: raise LookupError -msg = message.Message(self, msg_p, db=self) +msg = message.StandaloneMessage(self, msg_p, db=self) return msg @property diff --git a/bindings/python-cffi/notmuch2/_message.py b/bindings/python-cffi/notmuch2/_message.py index c5fdbf6d..416ce7ca 100644 --- a/bindings/python-cffi/notmuch2/_message.py +++ b/bindings/python-cffi/notmuch2/_message.py @@ -14,7 +14,7 @@ __all__ = ['Message'] class Message(base.NotmuchObject): -"""An email message stored in the notmuch database. +"""An email message stored in the notmuch database retrieved via a query. This should not be directly created, instead it will be returned by calling methods on :class:`Database`. A message keeps a @@ -61,22 +61,10 @@ class Message(base.NotmuchObject): @property def alive(self): -if not self._parent.alive: -return False -try: -self._msg_p -except errors.ObjectDestroyedError: -return False -else: -return True - -def __del__(self): -self._destroy() +return self._parent.alive def _destroy(self): -if self.alive: -capi.lib.notmuch_message_destroy(self._msg_p) -self._msg_p = None +pass @property def messageid(self): @@ -375,6 +363,30 @@ class Message(base.NotmuchObject): if isinstance(other, self.__class__): return self.messageid == other.messageid +class StandaloneMessage(Message): +"""An email message stored in the notmuch database. + +This subclass of Message is used for messages that are retrieved from the +database directly and are not owned by a query. +""" +@property +def alive(self): +if not self._parent.alive: +return False +try: +self._msg_p +except errors.ObjectDestroyedError: +return False +else: +return True + +def __del__(self): +self._destroy() + +def _destroy(self): +if self.alive: +capi.lib.notmuch_message_destroy(self._msg_p) +self._msg_p = None class FilenamesIter(base.NotmuchIter): """Iterator for binary filenames objects.""" -- 2.20.1 ___ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch
[PATCH 2/2] python/notmuch2: add bindings for the database config strings
--- bindings/python-cffi/notmuch2/_build.py| 17 + bindings/python-cffi/notmuch2/_config.py | 84 ++ bindings/python-cffi/notmuch2/_database.py | 23 ++ 3 files changed, 124 insertions(+) create mode 100644 bindings/python-cffi/notmuch2/_config.py diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py index 5e1fcac1..f269f2a1 100644 --- a/bindings/python-cffi/notmuch2/_build.py +++ b/bindings/python-cffi/notmuch2/_build.py @@ -314,6 +314,23 @@ ffibuilder.cdef( notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts); void notmuch_indexopts_destroy (notmuch_indexopts_t *options); + +notmuch_status_t +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_bool_t +notmuch_config_list_valid (notmuch_config_list_t *config_list); +const char * +notmuch_config_list_key (notmuch_config_list_t *config_list); +const char * +notmuch_config_list_value (notmuch_config_list_t *config_list); +void +notmuch_config_list_move_to_next (notmuch_config_list_t *config_list); +void +notmuch_config_list_destroy (notmuch_config_list_t *config_list); """ ) diff --git a/bindings/python-cffi/notmuch2/_config.py b/bindings/python-cffi/notmuch2/_config.py new file mode 100644 index ..58383c16 --- /dev/null +++ b/bindings/python-cffi/notmuch2/_config.py @@ -0,0 +1,84 @@ +import collections.abc + +import notmuch2._base as base +import notmuch2._capi as capi +import notmuch2._errors as errors + +__all__ = ['ConfigMapping'] + +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) + +def __next__(self): +item = super().__next__() +return base.BinString.from_cffi(item) + +class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping): +"""The config key/value pairs stored in the database. + +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. + +: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): +self._parent = parent +self._ptr = lambda: getattr(parent, ptr_name) + +@property +def alive(self): +return self._parent.alive + +def _destroy(self): +pass + +def __getitem__(self, key): +if isinstance(key, str): +key = key.encode('utf-8') +val_pp = capi.ffi.new('char**') +ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp) +if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: +raise errors.NotmuchError(ret) +if val_pp[0] == "": +capi.lib.free(val_pp[0]) +raise KeyError +val = base.BinString.from_cffi(val_pp[0]) +capi.lib.free(val_pp[0]) +return val + +def __setitem__(self, key, val): +if isinstance(key, str): +key = key.encode('utf-8') +if isinstance(val, str): +val = val.encode('utf-8') +ret = capi.lib.notmuch_database_set_config(self._ptr(), key, val) +if ret != capi.lib.NOTMUCH_STATUS_SUCCESS: +raise errors.NotmuchError(ret) + +def __delitem__(self, key): +self[key] = "" + +def __iter__(self): +"""Return an iterator over the config items. + +: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]) + +def __len__(self): +return sum(1 for t in self) diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py index f14eac78..fc55fea8 100644 --- a/bindings/python-cffi/notmuch2/_database.py +++ b/bindings/python-cffi/notmuch2/_database.py @@ -7,6 +7,7 @@ import pathlib import weakref import notmuch2._ba