Skip to content

Commit

Permalink
config: support comma in config ListAttribute item
Browse files Browse the repository at this point in the history
This is a first step towards completing sopel-irc#1455, since, as @dgw mentioned,
to do so would require accepting commas in a single entry of
`ListAttribute`.

The following tests are now added/updated for "`ListAttribute` items...":

- ... containing comma(s)
- ... with a non-escape backslash
- ... with escape sequences
- ... ending in special characters (escape character and delimiter)
- ... containing adjacent special characters
  • Loading branch information
HumorBaby committed Feb 2, 2019
1 parent 8febccd commit c262390
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 3 deletions.
29 changes: 27 additions & 2 deletions sopel/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,34 @@ class ListAttribute(BaseValidated):
currently support commas within items in the list. By default, the spaces
before and after each item are stripped; you can override this by passing
``strip=False``."""

ESCAPE_CHARACTER = '\\'
DELIMITER = ','

def __init__(self, name, strip=True, default=None):
default = default or []
super(ListAttribute, self).__init__(name, default=default)
self.strip = strip

def parse(self, value):
value = list(filter(None, value.split(',')))
items = []
is_escape_on = False
current_token = []
for char in value:
if not is_escape_on:
if char == ListAttribute.ESCAPE_CHARACTER:
is_escape_on = True
elif char == ListAttribute.DELIMITER:
items.append(''.join(current_token))
current_token = []
else:
current_token.append(char)
else:
current_token.append(char)
is_escape_on = False
items.append(''.join(current_token))

value = list(filter(None, items))
if self.strip:
return [v.strip() for v in value]
else:
Expand All @@ -227,7 +248,11 @@ def parse(self, value):
def serialize(self, value):
if not isinstance(value, (list, set)):
raise ValueError('ListAttribute value must be a list.')
return ','.join(value)

items = value
for special_char in [ListAttribute.ESCAPE_CHARACTER, ListAttribute.DELIMITER]:
items = map(lambda item: item.replace(special_char, '{}{}'.format(ListAttribute.ESCAPE_CHARACTER, special_char)), items)
return ','.join(items)

def configure(self, prompt, default, parent, section_name):
each_prompt = '?'
Expand Down
45 changes: 44 additions & 1 deletion test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,50 @@ def test_listattribute_with_multiple_values(self):

def test_listattribute_with_value_containing_comma(self):
self.config.fake.listattr = ['spam, egg, sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg', 'sausage', 'bacon'])
self.assertEqual(self.config.fake.listattr, ['spam, egg, sausage', 'bacon'])

def test_listattribute_with_value_containing_nonescape_backslash(self):
self.config.fake.listattr = ['spam', r'egg\sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg\sausage', 'bacon'])

self.config.fake.listattr = ['spam', r'egg\tacos', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg\tacos', 'bacon'])

def test_listattribute_with_value_containing_standard_escape_sequence(self):
self.config.fake.listattr = ['spam', 'egg\tsausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg\tsausage', 'bacon'])

self.config.fake.listattr = ['spam', 'egg\nsausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg\nsausage', 'bacon'])

self.config.fake.listattr = ['spam', 'egg\\sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg\\sausage', 'bacon'])

def test_listattribute_with_value_ending_in_special_chars(self):
self.config.fake.listattr = ['spam', 'egg', 'sausage\\', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg', 'sausage\\', 'bacon'])

self.config.fake.listattr = ['spam', 'egg', 'sausage,', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg', 'sausage,', 'bacon'])

self.config.fake.listattr = ['spam', 'egg', 'sausage,,', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg', 'sausage,,', 'bacon'])

def test_listattribute_with_value_containing_adjacent_special_chars(self):
self.config.fake.listattr = ['spam', r'egg\,sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg\,sausage', 'bacon'])

self.config.fake.listattr = ['spam', r'egg\,\sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg\,\sausage', 'bacon'])

self.config.fake.listattr = ['spam', r'egg,\,sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg,\,sausage', 'bacon'])

self.config.fake.listattr = ['spam', 'egg,,sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', 'egg,,sausage', 'bacon'])

self.config.fake.listattr = ['spam', r'egg\\sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg\\sausage', 'bacon'])

def test_choiceattribute_when_none(self):
self.config.fake.choiceattr = None
Expand Down

0 comments on commit c262390

Please sign in to comment.