From 3e3685b68bfcd1e21de69e067b5abf6b50d627ce Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Jun 2017 12:20:44 +0200 Subject: [PATCH] Initial configexc refactoring --- qutebrowser/browser/commands.py | 1 + qutebrowser/commands/runners.py | 4 +- qutebrowser/completion/completiondelegate.py | 13 ++- qutebrowser/config/config.py | 71 +-------------- qutebrowser/config/configexc.py | 29 +----- qutebrowser/config/newconfig.py | 92 ++++++++++++++++++-- qutebrowser/utils/urlutils.py | 6 +- tests/helpers/stubs.py | 2 + tests/unit/config/test_configexc.py | 18 +--- 9 files changed, 106 insertions(+), 130 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index c375188f4..185b7f5cd 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1541,6 +1541,7 @@ class CommandDispatcher: command)) path = 'commands.html#{}'.format(command) elif '->' in topic: + # FIXME:conf refactor parts = topic.split('->') if len(parts) != 2: raise cmdexc.CommandError("Invalid help topic {}!".format( diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index c9109ab78..79da78fbd 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -110,8 +110,8 @@ class CommandRunner(QObject): return default parts = text.strip().split(maxsplit=1) try: - alias = config.get('aliases', parts[0]) - except (configexc.NoOptionError, configexc.NoSectionError): + alias = config.val.aliases[parts[0]] + except KeyError: return default try: new_cmd = '{} {}'.format(alias, parts[1]) diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index f17a77cbf..d0de544a7 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -147,16 +147,15 @@ class CompletionItemDelegate(QStyledItemDelegate): # We can't use drawContents because then the color would be ignored. clip = QRectF(0, 0, rect.width(), rect.height()) self._painter.save() + if self._opt.state & QStyle.State_Selected: - option = 'completion.item.selected.fg' + color = config.val.completion.item.selected.fg elif not self._opt.state & QStyle.State_Enabled: - option = 'completion.category.fg' + color = config.val.completion.category.fg else: - option = 'completion.fg' - try: - self._painter.setPen(config.get('colors', option)) - except configexc.NoOptionError: - self._painter.setPen(config.val.colors.completion.fg) + color = config.val.completion.fg + self._painter.setPen(color) + ctx = QAbstractTextDocumentLayout.PaintContext() ctx.palette.setColor(QPalette.Text, self._painter.pen().color()) if clip.isValid(): diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 7a5dec930..f7b9cd93e 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -46,6 +46,9 @@ from qutebrowser.utils import (message, objreg, utils, standarddir, log, from qutebrowser.misc import objects from qutebrowser.utils.usertypes import Completion +# FIXME:conf compat +from qutebrowser.config.newconfig import change_filter + UNSET = object() @@ -54,74 +57,6 @@ UNSET = object() val = None -class change_filter: # pylint: disable=invalid-name - - """Decorator to filter calls based on a config section/option matching. - - This could also be a function, but as a class (with a "wrong" name) it's - much cleaner to implement. - - Attributes: - _option: An option or prefix to be filtered - _function: Whether a function rather than a method is decorated. - """ - - def __init__(self, option, function=False): - """Save decorator arguments. - - Gets called on parse-time with the decorator arguments. - - Args: - option: The option to be filtered. - function: Whether a function rather than a method is decorated. - """ - if (option not in configdata.DATA and - not configdata.is_valid_prefix(option)): - raise configexc.NoOptionError(option) - self._option = option - self._function = function - - def _check_match(self, option): - """Check if the given option matches the filter.""" - if option is None: - # Called directly, not from a config change event. - return True - elif option == self._option: - return True - elif option.startswith(self._option + '.'): - # prefix match - return True - else: - return False - - def __call__(self, func): - """Filter calls to the decorated function. - - Gets called when a function should be decorated. - - Adds a filter which returns if we're not interested in the change-event - and calls the wrapped function if we are. - - We assume the function passed doesn't take any parameters. - - Args: - func: The function to be decorated. - - Return: - The decorated function. - """ - if self._function: - @functools.wraps(func) - def wrapper(option=None): - if self._check_match(option): - return func() - else: - @functools.wraps(func) - def wrapper(wrapper_self, option=None): - if self._check_match(option): - return func(wrapper_self) - - return wrapper def get(*args, **kwargs): diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index b19d45d7b..c13d94d25 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -52,35 +52,10 @@ class ValidationError(Error): self.option = None -class NoSectionError(Error): - - """Raised when no section matches a requested option.""" - - def __init__(self, section): - super().__init__("Section {!r} does not exist!".format(section)) - self.section = section - - class NoOptionError(Error): """Raised when an option was not found.""" - def __init__(self, option, section): - super().__init__("No option {!r} in section {!r}".format( - option, section)) + def __init__(self, option): + super().__init__("No option {!r}".format(option)) self.option = option - self.section = section - - -class InterpolationSyntaxError(Error): - - """Raised when the source text contains invalid syntax. - - Current implementation raises this exception when the source text into - which substitutions are made does not conform to the required syntax. - """ - - def __init__(self, option, section, msg): - super().__init__(msg) - self.option = option - self.section = section diff --git a/qutebrowser/config/newconfig.py b/qutebrowser/config/newconfig.py index ae60e164c..6ceeb7b57 100644 --- a/qutebrowser/config/newconfig.py +++ b/qutebrowser/config/newconfig.py @@ -19,20 +19,17 @@ """New qutebrowser configuration code.""" +import functools from PyQt5.QtCore import pyqtSignal, QObject - -from qutebrowser.config import configdata +from qutebrowser.config import configdata, configexc from qutebrowser.utils import utils, objreg # An easy way to access the config from other code via config.val.foo val = None - -class UnknownOptionError(Exception): - - """Raised by NewConfigManager when an option is unknown.""" +_change_filters = [] class SectionStub: @@ -47,6 +44,83 @@ class SectionStub: return self._conf.get(self._name, item) +class change_filter: # pylint: disable=invalid-name + + """Decorator to filter calls based on a config section/option matching. + + This could also be a function, but as a class (with a "wrong" name) it's + much cleaner to implement. + + Attributes: + _option: An option or prefix to be filtered + _function: Whether a function rather than a method is decorated. + """ + + def __init__(self, option, function=False): + """Save decorator arguments. + + Gets called on parse-time with the decorator arguments. + + Args: + option: The option to be filtered. + function: Whether a function rather than a method is decorated. + """ + self._option = option + self._function = function + _change_filters.append(self) + + def validate(self): + """Make sure the configured option or prefix exists. + + We can't do this in __init__ as configdata isn't ready yet. + """ + if (self._option not in configdata.DATA and + not configdata.is_valid_prefix(self._option)): + raise configexc.NoOptionError(self._option) + + def _check_match(self, option): + """Check if the given option matches the filter.""" + if option is None: + # Called directly, not from a config change event. + return True + elif option == self._option: + return True + elif option.startswith(self._option + '.'): + # prefix match + return True + else: + return False + + def __call__(self, func): + """Filter calls to the decorated function. + + Gets called when a function should be decorated. + + Adds a filter which returns if we're not interested in the change-event + and calls the wrapped function if we are. + + We assume the function passed doesn't take any parameters. + + Args: + func: The function to be decorated. + + Return: + The decorated function. + """ + if self._function: + @functools.wraps(func) + def wrapper(option=None): + if self._check_match(option): + return func() + else: + @functools.wraps(func) + def wrapper(wrapper_self, option=None): + if self._check_match(option): + return func(wrapper_self) + + return wrapper + + class NewConfigManager(QObject): # FIXME:conf QObject? @@ -65,7 +139,7 @@ class NewConfigManager(QObject): try: val = self._values[option] except KeyError as e: - raise UnknownOptionError(e) + raise configexc.NoOptionError(e) return val.typ.from_py(val.default) @@ -117,5 +191,9 @@ def init(parent): new_config = NewConfigManager(parent) new_config.read_defaults() objreg.register('config', new_config) + global val val = ConfigContainer(new_config) + + for cf in _change_filters: + cf.validate() diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 5adc5d9c7..7ba457e2d 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -29,7 +29,7 @@ import urllib.parse from PyQt5.QtCore import QUrl from PyQt5.QtNetwork import QHostInfo, QHostAddress, QNetworkProxy -from qutebrowser.config import config, configexc +from qutebrowser.config import config from qutebrowser.utils import log, qtutils, message, utils from qutebrowser.commands import cmdexc from qutebrowser.browser.network import pac @@ -70,8 +70,8 @@ def _parse_search_term(s): if len(split) == 2: engine = split[0] try: - config.get('searchengines', engine) - except configexc.NoOptionError: + config.val.searchengines[engine] + except KeyError: engine = None term = s else: diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 0551f5a86..dbab2e5f9 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -419,6 +419,8 @@ class ConfigStub(QObject): data: The config data to return. """ + # FIXME:conf refactor... + changed = pyqtSignal(str, str) def __init__(self, parent=None): diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py index 330ad7b07..a6c02f72e 100644 --- a/tests/unit/config/test_configexc.py +++ b/tests/unit/config/test_configexc.py @@ -29,24 +29,10 @@ def test_validation_error(): assert str(e) == "Invalid value 'val' - msg" -def test_no_section_error(): - e = configexc.NoSectionError('sect') - assert e.section == 'sect' - assert str(e) == "Section 'sect' does not exist!" - - def test_no_option_error(): - e = configexc.NoOptionError('opt', 'sect') - assert e.section == 'sect' + e = configexc.NoOptionError('opt') assert e.option == 'opt' - assert str(e) == "No option 'opt' in section 'sect'" - - -def test_interpolation_syntax_error(): - e = configexc.InterpolationSyntaxError('opt', 'sect', 'msg') - assert e.section == 'sect' - assert e.option == 'opt' - assert str(e) == 'msg' + assert str(e) == "No option 'opt'" def test_backend_error():