summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2021-11-20 14:59:34 +0100
committerAnton Khirnov <anton@khirnov.net>2021-11-20 14:59:34 +0100
commit2b6c9088c78d1aa944eeb969f678d644b8ef1180 (patch)
tree294a983822471a2d65581fdf945b457fc724cb3f
parentd64e41150f6ba53a9361e8560d374bc69974ca84 (diff)
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.
-rw-r--r--alot/commands/envelope.py60
-rw-r--r--alot/defaults/alot.rc.spec7
-rw-r--r--alot/settings/manager.py10
-rw-r--r--alot/utils/collections.py29
4 files changed, 51 insertions, 55 deletions
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 <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))