From 2b6c9088c78d1aa944eeb969f678d644b8ef1180 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Sat, 20 Nov 2021 14:59:34 +0100 Subject: commands/envelope: refactor deriving headers to be edited Split the code into its own function. Make it properly case-insensitive. Apply either the blacklist or the whitelist setting, trying to apply both makes no sense. Enforce that either a blacklist or a whitelist is configured in the settings. --- alot/commands/envelope.py | 60 ++++++++++++++++++++++++++++------------------ alot/defaults/alot.rc.spec | 7 +++--- alot/settings/manager.py | 10 ++++++++ alot/utils/collections.py | 29 ---------------------- 4 files changed, 51 insertions(+), 55 deletions(-) delete mode 100644 alot/utils/collections.py diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py index 5dce6b44..c347c3c7 100644 --- a/alot/commands/envelope.py +++ b/alot/commands/envelope.py @@ -26,7 +26,6 @@ from ..errors import GPGProblem from ..settings.const import settings from ..settings.errors import NoMatchingAccount from ..utils import argparse as cargparse -from ..utils.collections import OrderedSet MODE = 'envelope' @@ -326,21 +325,41 @@ class EditCommand(Command): self.edit_only_body = False super().__init__(**kwargs) + def _headers_to_edit(self, headers): + whitelist = settings.get('edit_headers_whitelist') + blacklist = settings.get('edit_headers_blacklist') + + # convert all header names to lowercase for uniqueness + wl = set(map(str.lower, whitelist)) + bl = set(map(str.lower, blacklist)) + kl = set(map(str.lower, headers)) + + # exactly one of wl/bl must be '*', so when operating + # in blacklist mode we convert the blacklist to whitelist + if '*' in wl: + wl = kl - bl + + # drop all non-whitelisted headers + edit_headers = headers.copy() + for k in set((k.lower() for k in edit_headers)): + if not k in wl: + del edit_headers[k] + + if not '*' in whitelist: + # any explicitly whitelisted headers that do not have a value + # are set to empty string, so they appear in the edited file + for h in whitelist: + if not h in edit_headers: + edit_headers[h] = '' + + logging.debug('editable headers: %s', edit_headers) + return edit_headers + async def apply(self, ui): ebuffer = ui.current_buffer if not self.envelope: self.envelope = ui.current_buffer.envelope - # determine editable headers - edit_headers = OrderedSet(settings.get('edit_headers_whitelist')) - if '*' in edit_headers: - edit_headers = OrderedSet(self.envelope.headers) - blacklist = set(settings.get('edit_headers_blacklist')) - if '*' in blacklist: - blacklist = set(self.envelope.headers) - edit_headers = edit_headers - blacklist - logging.info('editable headers: %s', edit_headers) - def openEnvelopeFromTmpfile(): # This parses the input from the tempfile. # we do this ourselves here because we want to be able to @@ -367,20 +386,15 @@ class EditCommand(Command): # decode header headertext = '' - for key in edit_headers: - vlist = self.envelope.get_all(key) - if len(vlist) == 0: - # ensure editable headers are present in template - vlist.append('') - else: - # remove to be edited lines from envelope + for key, val in self._headers_to_edit(self.envelope.headers).items(): + # remove to be edited lines from envelope + if key in self.envelope: del self.envelope[key] - for value in vlist: - # newlines (with surrounding spaces) by spaces in values - value = value.strip() - value = re.sub('[ \t\r\f\v]*\n[ \t\r\f\v]*', ' ', value) - headertext += '%s: %s\n' % (key, value) + # newlines (with surrounding spaces) by spaces in values + val = val.strip() + val = re.sub('[ \t\r\f\v]*\n[ \t\r\f\v]*', ' ', val) + headertext += '%s: %s\n' % (key, val) # determine editable content bodytext = self.envelope.body diff --git a/alot/defaults/alot.rc.spec b/alot/defaults/alot.rc.spec index 6531f76a..ca890eb6 100644 --- a/alot/defaults/alot.rc.spec +++ b/alot/defaults/alot.rc.spec @@ -114,9 +114,10 @@ editor_spawn = boolean(default=False) # will make alot non-blocking during edits editor_in_thread = boolean(default=False) -# Which header fields should be editable in your editor -# used are those that match the whitelist and don't match the blacklist. -# in both cases '*' may be used to indicate all fields. +# Which header fields should be editable in your editor. +# Exactly one of edit_headers_whitelist and edit_headers_blacklist must be '*', +# then the other one is used to either whitelist or blacklist headers for +# editing. edit_headers_whitelist = force_list(default=list(*,)) # see :ref:`edit_headers_whitelist ` diff --git a/alot/settings/manager.py b/alot/settings/manager.py index f8ade6df..e4fa48d7 100644 --- a/alot/settings/manager.py +++ b/alot/settings/manager.py @@ -154,6 +154,16 @@ class SettingsManager: tempdir = self._config.get('template_dir') logging.debug('template directory: `%s`' % tempdir) + # validate edit_headers_whitelist/blacklist, which depend on each other + wl = self._config.get('edit_headers_whitelist') + bl = self._config.get('edit_headers_blacklist') + if (wl == ['*']) == (bl == ['*']): + raise ConfigError('Exactly one of "edit_headers_whitelist"', + '"edit_headers_blacklist" must be "*"') + if ('*' in wl and len(wl) > 1) or ('*' in bl and len(bl) > 1): + raise ConfigError('When "*" is used in "edit_headers_whitelist"/' + '"edit_headers_blacklist", it must be the only item.') + # themes themestring = newconfig['theme'] themes_dir = self._config.get('themes_dir') diff --git a/alot/utils/collections.py b/alot/utils/collections.py deleted file mode 100644 index acde56ce..00000000 --- a/alot/utils/collections.py +++ /dev/null @@ -1,29 +0,0 @@ -# This file is released under the GNU GPL, version 3 or a later revision. -# For further details see the COPYING file - -from collections.abc import Set - -# for backward compatibility with Python <3.7 -from collections import OrderedDict - - -class OrderedSet(Set): - """ - Ordered collection of distinct hashable objects. - Taken from https://stackoverflow.com/a/10006674 - """ - - def __init__(self, iterable=()): - self.d = OrderedDict.fromkeys(iterable) - - def __len__(self): - return len(self.d) - - def __contains__(self, element): - return element in self.d - - def __iter__(self): - return iter(self.d) - - def __repr__(self): - return str(list(self)) -- cgit v1.2.3