Allow direct values for url.start_pages and content.user_stylesheets

This commit is contained in:
Florian Bruhin 2017-09-27 08:21:03 +02:00
parent 2dfcf9c506
commit c694bff902
5 changed files with 168 additions and 11 deletions

View File

@ -1944,7 +1944,7 @@ Default: +pass:[ask]+
=== content.user_stylesheets
A list of user stylesheet filenames to use.
Type: <<types,List>>
Type: <<types,ListOrValue>>
Default: empty
@ -3056,11 +3056,9 @@ Default:
=== url.start_pages
The page(s) to open at the start.
Type: <<types,List>>
Type: <<types,ListOrValue>>
Default:
- +pass:[https://start.duckduckgo.com]+
Default: +pass:[https://start.duckduckgo.com]+
[[url.yank_ignored_parameters]]
=== url.yank_ignored_parameters
@ -3199,6 +3197,7 @@ Lists with duplicate flags are invalid. Each item is checked against the valid v
|List|A list of values.
When setting from a string, pass a json-like list, e.g. `["one", "two"]`.
|ListOrValue|A list of values, or a single value.
|NewTabPosition|How new tabs are positioned.
|Padding|Setting for paddings around elements.
|Perc|A percentage.

View File

@ -93,7 +93,7 @@ def _parse_yaml_type(name, node):
if typ is configtypes.Dict:
kwargs['keytype'] = _parse_yaml_type(name, kwargs['keytype'])
kwargs['valtype'] = _parse_yaml_type(name, kwargs['valtype'])
elif typ is configtypes.List:
elif typ is configtypes.List or typ is configtypes.ListOrValue:
kwargs['valtype'] = _parse_yaml_type(name, kwargs['valtype'])
except KeyError as e:
_raise_invalid_node(name, str(e), node)

View File

@ -545,7 +545,7 @@ content.ssl_strict:
content.user_stylesheets:
type:
name: List
name: ListOrValue
valtype: File
none_ok: True
default: null
@ -1240,9 +1240,9 @@ url.searchengines:
url.start_pages:
type:
name: List
name: ListOrValue
valtype: FuzzyUrl
default: ["https://start.duckduckgo.com"]
default: "https://start.duckduckgo.com"
desc: The page(s) to open at the start.
url.yank_ignored_parameters:

View File

@ -476,6 +476,67 @@ class List(BaseType):
return '\n'.join(lines)
class ListOrValue(BaseType):
"""A list of values, or a single value.
//
Internally, the value is stored as either a value (of valtype), or a list.
to_py() then ensures that it's always a list.
"""
_show_valtype = True
def __init__(self, valtype, none_ok=False, *args, **kwargs):
super().__init__(none_ok)
assert not isinstance(valtype, (List, ListOrValue)), valtype
self.listtype = List(valtype, none_ok=none_ok, *args, **kwargs)
self.valtype = valtype
def get_name(self):
return self.listtype.get_name() + ' or ' + self.valtype.get_name()
def get_valid_values(self):
return self.valtype.get_valid_values()
def from_str(self, value):
try:
return self.listtype.from_str(value)
except configexc.ValidationError:
return self.valtype.from_str(value)
def to_py(self, value):
try:
return [self.valtype.to_py(value)]
except configexc.ValidationError:
return self.listtype.to_py(value)
def to_str(self, value):
if value is None:
return ''
if isinstance(value, list):
if len(value) == 1:
return self.valtype.to_str(value[0])
else:
return self.listtype.to_str(value)
else:
return self.valtype.to_str(value)
def to_doc(self, value):
if value is None:
return 'empty'
if isinstance(value, list):
if len(value) == 1:
return self.valtype.to_doc(value[0])
else:
return self.listtype.to_doc(value)
else:
return self.valtype.to_doc(value)
class FlagList(List):
"""A list of flags.

View File

@ -193,7 +193,8 @@ class TestAll:
if member in [configtypes.BaseType, configtypes.MappingType,
configtypes._Numeric]:
pass
elif member is configtypes.List:
elif (member is configtypes.List or
member is configtypes.ListOrValue):
yield functools.partial(member, valtype=configtypes.Int())
yield functools.partial(member, valtype=configtypes.Url())
elif member is configtypes.Dict:
@ -240,6 +241,9 @@ class TestAll:
configtypes.PercOrInt, # ditto
]:
return
if (isinstance(typ, configtypes.ListOrValue) and
isinstance(typ.valtype, configtypes.Int)):
return
assert converted == s
@ -250,7 +254,7 @@ class TestAll:
to_py_expected = configtypes.PaddingValues(None, None, None, None)
elif isinstance(typ, configtypes.Dict):
to_py_expected = {}
elif isinstance(typ, configtypes.List):
elif isinstance(typ, (configtypes.List, configtypes.ListOrValue)):
to_py_expected = []
else:
to_py_expected = None
@ -670,6 +674,99 @@ class TestFlagList:
assert klass().complete() is None
class TestListOrValue:
@pytest.fixture
def klass(self):
return configtypes.ListOrValue
@pytest.fixture
def strtype(self):
return configtypes.String()
@pytest.mark.parametrize('val, expected', [
('["foo"]', ['foo']),
('["foo", "bar"]', ['foo', 'bar']),
('foo', 'foo'),
])
def test_from_str(self, klass, strtype, val, expected):
assert klass(strtype).from_str(val) == expected
def test_from_str_invalid(self, klass):
valtype = configtypes.String(minlen=10)
with pytest.raises(configexc.ValidationError):
klass(valtype).from_str('123')
@pytest.mark.parametrize('val, expected', [
(['foo'], ['foo']),
('foo', ['foo']),
])
def test_to_py_valid(self, klass, strtype, val, expected):
assert klass(strtype).to_py(val) == expected
@pytest.mark.parametrize('val', [[42], ['\U00010000']])
def test_to_py_invalid(self, klass, strtype, val):
with pytest.raises(configexc.ValidationError):
klass(strtype).to_py(val)
@pytest.mark.parametrize('val', [None, ['foo', 'bar'], 'abcd'])
def test_to_py_length(self, strtype, klass, val):
klass(strtype, none_ok=True, length=2).to_py(val)
@pytest.mark.parametrize('val', [['a'], ['a', 'b'], ['a', 'b', 'c', 'd']])
def test_wrong_length(self, strtype, klass, val):
with pytest.raises(configexc.ValidationError,
match='Exactly 3 values need to be set!'):
klass(strtype, length=3).to_py(val)
def test_get_name(self, strtype, klass):
assert klass(strtype).get_name() == 'List of String or String'
def test_get_valid_values(self, klass):
valid_values = configtypes.ValidValues('foo', 'bar', 'baz')
valtype = configtypes.String(valid_values=valid_values)
assert klass(valtype).get_valid_values() == valid_values
def test_to_str(self, strtype, klass):
assert klass(strtype).to_str(["a", True]) == '["a", true]'
@hypothesis.given(val=strategies.lists(strategies.just('foo')))
def test_hypothesis(self, strtype, klass, val):
typ = klass(strtype, none_ok=True)
try:
converted = typ.to_py(val)
except configexc.ValidationError:
pass
else:
expected = converted if converted else []
assert typ.to_py(typ.from_str(typ.to_str(converted))) == expected
@hypothesis.given(val=strategies.lists(strategies.just('foo')))
def test_hypothesis_text(self, strtype, klass, val):
typ = klass(strtype)
text = json.dumps(val)
try:
typ.to_str(typ.from_str(text))
except configexc.ValidationError:
pass
@pytest.mark.parametrize('val, expected', [
# simple list
(['foo', 'bar'], '\n\n- +pass:[foo]+\n- +pass:[bar]+'),
# only one value
(['foo'], '+pass:[foo]+'),
# value without list
('foo', '+pass:[foo]+'),
# empty
([], 'empty'),
(None, 'empty'),
])
def test_to_doc(self, klass, strtype, val, expected):
doc = klass(strtype).to_doc(val)
print(doc)
assert doc == expected
class TestBool:
TESTS = {