Add config.pattern()

This commit is contained in:
Florian Bruhin 2018-02-20 20:54:26 +01:00
parent de38566c11
commit 439d51875f
5 changed files with 59 additions and 15 deletions

View File

@ -185,6 +185,15 @@ https://developer.chrome.com/apps/match_patterns[URL pattern]:
config.set('content.images', False, '*://example.com/')
----
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
similar to `c.` which is scoped to the given domain:
[source,python]
----
with config.pattern('*://example.com/') as p:
p.content.images = False
----
Binding keys
~~~~~~~~~~~~

View File

@ -466,16 +466,21 @@ class ConfigContainer:
_prefix: The __getattr__ chain leading up to this object.
_configapi: If given, get values suitable for config.py and
add errors to the given ConfigAPI object.
_pattern: The URL pattern to be used.
"""
def __init__(self, config, configapi=None, prefix=''):
def __init__(self, config, configapi=None, prefix='', pattern=None):
self._config = config
self._prefix = prefix
self._configapi = configapi
self._pattern = pattern
if configapi is None and pattern is not None:
raise TypeError("Can't use pattern without configapi!")
def __repr__(self):
return utils.get_repr(self, constructor=True, config=self._config,
configapi=self._configapi, prefix=self._prefix)
configapi=self._configapi, prefix=self._prefix,
pattern=self._pattern)
@contextlib.contextmanager
def _handle_error(self, action, name):
@ -503,7 +508,7 @@ class ConfigContainer:
if configdata.is_valid_prefix(name):
return ConfigContainer(config=self._config,
configapi=self._configapi,
prefix=name)
prefix=name, pattern=self._pattern)
with self._handle_error('getting', name):
if self._configapi is None:
@ -511,7 +516,8 @@ class ConfigContainer:
return self._config.get(name)
else:
# access from config.py
return self._config.get_mutable_obj(name)
return self._config.get_mutable_obj(
name, pattern=self._pattern)
def __setattr__(self, attr, value):
"""Set the given option in the config."""
@ -521,7 +527,7 @@ class ConfigContainer:
name = self._join(attr)
with self._handle_error('setting', name):
self._config.set_obj(name, value)
self._config.set_obj(name, value, pattern=self._pattern)
def _join(self, attr):
"""Get the prefix joined with the given attribute."""

View File

@ -348,6 +348,16 @@ class ConfigAPI:
except configexc.ConfigFileErrors as e:
self.errors += e.errors
@contextlib.contextmanager
def pattern(self, pattern):
"""Get a ConfigContainer for the given pattern."""
# We need to propagate the exception so we don't need to return
# something.
urlpattern = urlmatch.UrlPattern(pattern)
container = config.ConfigContainer(config=self._config, configapi=self,
pattern=urlpattern)
yield container
class ConfigPyWriter:

View File

@ -637,6 +637,12 @@ class TestContainer:
assert error.text == "While getting 'tabs.foobar'"
assert str(error.exception) == "No option 'tabs.foobar'"
def test_pattern_no_configapi(self, config_stub):
pattern = urlmatch.UrlPattern('https://example.com/')
with pytest.raises(TypeError,
match="Can't use pattern without configapi!"):
config.ConfigContainer(config_stub, pattern=pattern)
class StyleObj(QObject):

View File

@ -507,17 +507,29 @@ class TestConfigPy:
confpy.read()
assert config.instance.get_obj('colors.hints.bg') == 'red'
def test_set_with_pattern(self, confpy):
@pytest.mark.parametrize('template', [
"config.set({opt!r}, False, {pattern!r})",
"with config.pattern({pattern!r}) as p: p.{opt} = False",
])
def test_set_with_pattern(self, confpy, template):
option = 'content.javascript.enabled'
pattern = 'https://www.example.com/'
confpy.write('config.set({!r}, False, {!r})'.format(option, pattern))
confpy.write(template.format(opt=option, pattern=pattern))
confpy.read()
assert config.instance.get_obj(option)
assert not config.instance.get_obj_for_pattern(
option, pattern=urlmatch.UrlPattern(pattern))
def test_set_context_manager_global(self, confpy):
"""When "with config.pattern" is used, "c." should still be global."""
option = 'content.javascript.enabled'
confpy.write('with config.pattern("https://www.example.com/") as p:'
' c.{} = False'.format(option))
confpy.read()
assert not config.instance.get_obj(option)
@pytest.mark.parametrize('set_first', [True, False])
@pytest.mark.parametrize('get_line', [
'c.colors.hints.fg',
@ -703,20 +715,21 @@ class TestConfigPy:
"'qt.args')")
assert str(error.exception) == expected
@pytest.mark.parametrize('line, action', [
('config.get("content.images", "://")', 'getting'),
('config.set("content.images", False, "://")', 'setting'),
@pytest.mark.parametrize('line, text', [
('config.get("content.images", "://")',
"While getting 'content.images' and parsing pattern"),
('config.set("content.images", False, "://")',
"While setting 'content.images' and parsing pattern"),
('with config.pattern("://"): pass',
"Unhandled exception"),
])
def test_invalid_pattern(self, confpy, line, action):
def test_invalid_pattern(self, confpy, line, text):
confpy.write(line)
error = confpy.read(error=True)
assert error.text == ("While {} 'content.images' and parsing pattern"
.format(action))
assert error.text == text
assert isinstance(error.exception, urlmatch.ParseError)
assert str(error.exception) == "No scheme given"
assert error.traceback is None
def test_multiple_errors(self, confpy):
confpy.write("c.foo = 42", "config.set('foo', 42)", "1/0")