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`.

Items entered through the wizard do not need to be escaped manually. Items configured in the `.cfg` file manually require an escaped command, like `\,`. The prompt has also been changed such that the items are now surrounded by quotes so that one can differentiate items that may contains commas.

The following scenarios were tested (using the `url.py` module, specifically the `exclude` setting):

1. Wizard, setting items when there is no default (preexisting items); mix of w/ commas and w/o commas
2. Wizard, replacing existing items when a default exists; mix of w/ commas and w/o commas
3. Wizard, extending the current default list; mix of w/ commas and w/o commas
4. Manual edit, setting items; w/ commas (manually escaped) and w/o commas
5. Wizard, setting items w/o default; no commas

Relevant tests have also been updated/added:

1. Item with comma remains single item:
    - `test_listattribute_with_value_containing_comma`
2. Item with backslash is correctly serialized/parsed:
    - `test_listattribute_with_value_containing_backslash`
    - `test_listattribute_with_value_ending_in_backslash`
  • Loading branch information
HumorBaby committed Jan 28, 2019
1 parent 8febccd commit a576a04
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 11 deletions.
26 changes: 16 additions & 10 deletions sopel/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from __future__ import unicode_literals, absolute_import, print_function, division

import os.path
import re
import sys
from sopel.tools import get_input

Expand Down Expand Up @@ -218,16 +219,18 @@ def __init__(self, name, strip=True, default=None):
self.strip = strip

def parse(self, value):
value = list(filter(None, value.split(',')))
value = list(filter(None, re.split(r'(?<!(?<!\\)\\),', value)))
items = map(lambda x: x.replace('\\\\', '\\').replace(r'\,', ','), value)
if self.strip:
return [v.strip() for v in value]
return [v.strip() for v in items]
else:
return value
return items

def serialize(self, value):
if not isinstance(value, (list, set)):
raise ValueError('ListAttribute value must be a list.')
return ','.join(value)
items = map(lambda x: x.replace('\\', '\\\\').replace(',', r'\,'), value)
return ','.join(items)

def configure(self, prompt, default, parent, section_name):
each_prompt = '?'
Expand All @@ -236,17 +239,20 @@ def configure(self, prompt, default, parent, section_name):
prompt = prompt[0]

if default is not NO_DEFAULT:
default = ','.join(default)
prompt = '{} [{}]'.format(prompt, default)
default_value = self.serialize(default)
prompt = '{} [{}]'.format(prompt, ",".join(map(lambda x: '"{}"'.format(x), default)))
else:
default = ''
default_value = ''
print(prompt)
values = []
value = get_input(each_prompt + ' ') or default
value = get_input(each_prompt + ' ') or default_value
while value:
values.append(value)
if value == default_value:
values.extend(self.parse(default_value))
else:
values.append(value)
value = get_input(each_prompt + ' ')
return self.parse(','.join(values))
return self.parse(self.serialize(values))


class ChoiceAttribute(BaseValidated):
Expand Down
10 changes: 9 additions & 1 deletion test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ 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_backslash(self):
self.config.fake.listattr = ['spam', r'egg\sausage', 'bacon']
self.assertEqual(self.config.fake.listattr, ['spam', r'egg\sausage', 'bacon'])

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

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

0 comments on commit a576a04

Please sign in to comment.