Revert "Add types for most of qutebrowser.config"

It breaks 'tsh' because of the *values annotation.
This reverts commit 208d3db475.
This commit is contained in:
Florian Bruhin 2018-12-03 13:17:23 +01:00
parent 208d3db475
commit 7494d238ce
9 changed files with 215 additions and 384 deletions

View File

@ -61,39 +61,3 @@ disallow_incomplete_defs = True
[mypy-qutebrowser.commands.cmdutils] [mypy-qutebrowser.commands.cmdutils]
disallow_untyped_defs = True disallow_untyped_defs = True
disallow_incomplete_defs = True disallow_incomplete_defs = True
[mypy-qutebrowser.config.config]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configcache]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configcommands]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configdata]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configdiff]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configexc]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configfiles]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configinit]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configutils]
disallow_untyped_defs = True
disallow_incomplete_defs = True

View File

@ -23,21 +23,18 @@ import copy
import contextlib import contextlib
import functools import functools
import typing import typing
from typing import Any
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
from qutebrowser.config import configdata, configexc, configutils from qutebrowser.config import configdata, configexc, configutils
from qutebrowser.utils import utils, log, jinja, urlmatch from qutebrowser.utils import utils, log, jinja
from qutebrowser.misc import objects from qutebrowser.misc import objects
from qutebrowser.keyinput import keyutils from qutebrowser.keyinput import keyutils
MYPY = False MYPY = False
if MYPY: # pragma: no cover if MYPY:
# pylint: disable=unused-import,useless-suppression # pylint: disable=unused-import
from typing import Tuple, MutableMapping from qutebrowser.config import configcache # pragma: no cover
from qutebrowser.config import configcache, configfiles
from qutebrowser.misc import savemanager
# An easy way to access the config from other code via config.val.foo # An easy way to access the config from other code via config.val.foo
val = typing.cast('ConfigContainer', None) val = typing.cast('ConfigContainer', None)
@ -64,7 +61,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
_function: Whether a function rather than a method is decorated. _function: Whether a function rather than a method is decorated.
""" """
def __init__(self, option: str, function: bool = False) -> None: def __init__(self, option, function=False):
"""Save decorator arguments. """Save decorator arguments.
Gets called on parse-time with the decorator arguments. Gets called on parse-time with the decorator arguments.
@ -77,7 +74,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
self._function = function self._function = function
change_filters.append(self) change_filters.append(self)
def validate(self) -> None: def validate(self):
"""Make sure the configured option or prefix exists. """Make sure the configured option or prefix exists.
We can't do this in __init__ as configdata isn't ready yet. We can't do this in __init__ as configdata isn't ready yet.
@ -86,7 +83,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
not configdata.is_valid_prefix(self._option)): not configdata.is_valid_prefix(self._option)):
raise configexc.NoOptionError(self._option) raise configexc.NoOptionError(self._option)
def _check_match(self, option: typing.Optional[str]) -> bool: def _check_match(self, option):
"""Check if the given option matches the filter.""" """Check if the given option matches the filter."""
if option is None: if option is None:
# Called directly, not from a config change event. # Called directly, not from a config change event.
@ -99,7 +96,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
else: else:
return False return False
def __call__(self, func: typing.Callable) -> typing.Callable: def __call__(self, func):
"""Filter calls to the decorated function. """Filter calls to the decorated function.
Gets called when a function should be decorated. Gets called when a function should be decorated.
@ -117,21 +114,20 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
""" """
if self._function: if self._function:
@functools.wraps(func) @functools.wraps(func)
def func_wrapper(option: str = None) -> typing.Any: def wrapper(option=None):
"""Call the underlying function.""" """Call the underlying function."""
if self._check_match(option): if self._check_match(option):
return func() return func()
return None return None
return func_wrapper
else: else:
@functools.wraps(func) @functools.wraps(func)
def meth_wrapper(wrapper_self: typing.Any, def wrapper(wrapper_self, option=None):
option: str = None) -> typing.Any:
"""Call the underlying function.""" """Call the underlying function."""
if self._check_match(option): if self._check_match(option):
return func(wrapper_self) return func(wrapper_self)
return None return None
return meth_wrapper
return wrapper
class KeyConfig: class KeyConfig:
@ -144,22 +140,17 @@ class KeyConfig:
_config: The Config object to be used. _config: The Config object to be used.
""" """
_ReverseBindings = typing.Dict[str, typing.MutableSequence[str]] def __init__(self, config):
def __init__(self, config: 'Config') -> None:
self._config = config self._config = config
def _validate(self, key: keyutils.KeySequence, mode: str) -> None: def _validate(self, key, mode):
"""Validate the given key and mode.""" """Validate the given key and mode."""
# Catch old usage of this code # Catch old usage of this code
assert isinstance(key, keyutils.KeySequence), key assert isinstance(key, keyutils.KeySequence), key
if mode not in configdata.DATA['bindings.default'].default: if mode not in configdata.DATA['bindings.default'].default:
raise configexc.KeybindingError("Invalid mode {}!".format(mode)) raise configexc.KeybindingError("Invalid mode {}!".format(mode))
def get_bindings_for( def get_bindings_for(self, mode):
self,
mode: str
) -> typing.Dict[keyutils.KeySequence, str]:
"""Get the combined bindings for the given mode.""" """Get the combined bindings for the given mode."""
bindings = dict(val.bindings.default[mode]) bindings = dict(val.bindings.default[mode])
for key, binding in val.bindings.commands[mode].items(): for key, binding in val.bindings.commands[mode].items():
@ -169,9 +160,9 @@ class KeyConfig:
bindings[key] = binding bindings[key] = binding
return bindings return bindings
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings': def get_reverse_bindings_for(self, mode):
"""Get a dict of commands to a list of bindings for the mode.""" """Get a dict of commands to a list of bindings for the mode."""
cmd_to_keys = {} # type: KeyConfig._ReverseBindings cmd_to_keys = {}
bindings = self.get_bindings_for(mode) bindings = self.get_bindings_for(mode)
for seq, full_cmd in sorted(bindings.items()): for seq, full_cmd in sorted(bindings.items()):
for cmd in full_cmd.split(';;'): for cmd in full_cmd.split(';;'):
@ -184,10 +175,7 @@ class KeyConfig:
cmd_to_keys[cmd].insert(0, str(seq)) cmd_to_keys[cmd].insert(0, str(seq))
return cmd_to_keys return cmd_to_keys
def get_command(self, def get_command(self, key, mode, default=False):
key: keyutils.KeySequence,
mode: str,
default: bool = False) -> str:
"""Get the command for a given key (or None).""" """Get the command for a given key (or None)."""
self._validate(key, mode) self._validate(key, mode)
if default: if default:
@ -196,11 +184,7 @@ class KeyConfig:
bindings = self.get_bindings_for(mode) bindings = self.get_bindings_for(mode)
return bindings.get(key, None) return bindings.get(key, None)
def bind(self, def bind(self, key, command, *, mode, save_yaml=False):
key: keyutils.KeySequence,
command: str, *,
mode: str,
save_yaml: bool = False) -> None:
"""Add a new binding from key to command.""" """Add a new binding from key to command."""
if command is not None and not command.strip(): if command is not None and not command.strip():
raise configexc.KeybindingError( raise configexc.KeybindingError(
@ -208,8 +192,8 @@ class KeyConfig:
'mode'.format(key, mode)) 'mode'.format(key, mode))
self._validate(key, mode) self._validate(key, mode)
log.keyboard.vdebug( # type: ignore log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
"Adding binding {} -> {} in mode {}.".format(key, command, mode)) key, command, mode))
bindings = self._config.get_mutable_obj('bindings.commands') bindings = self._config.get_mutable_obj('bindings.commands')
if mode not in bindings: if mode not in bindings:
@ -217,10 +201,7 @@ class KeyConfig:
bindings[mode][str(key)] = command bindings[mode][str(key)] = command
self._config.update_mutables(save_yaml=save_yaml) self._config.update_mutables(save_yaml=save_yaml)
def bind_default(self, def bind_default(self, key, *, mode='normal', save_yaml=False):
key: keyutils.KeySequence, *,
mode: str = 'normal',
save_yaml: bool = False) -> None:
"""Restore a default keybinding.""" """Restore a default keybinding."""
self._validate(key, mode) self._validate(key, mode)
@ -232,10 +213,7 @@ class KeyConfig:
"Can't find binding '{}' in {} mode".format(key, mode)) "Can't find binding '{}' in {} mode".format(key, mode))
self._config.update_mutables(save_yaml=save_yaml) self._config.update_mutables(save_yaml=save_yaml)
def unbind(self, def unbind(self, key, *, mode='normal', save_yaml=False):
key: keyutils.KeySequence, *,
mode: str = 'normal',
save_yaml: bool = False) -> None:
"""Unbind the given key in the given mode.""" """Unbind the given key in the given mode."""
self._validate(key, mode) self._validate(key, mode)
@ -276,27 +254,24 @@ class Config(QObject):
MUTABLE_TYPES = (dict, list) MUTABLE_TYPES = (dict, list)
changed = pyqtSignal(str) changed = pyqtSignal(str)
def __init__(self, def __init__(self, yaml_config, parent=None):
yaml_config: 'configfiles.YamlConfig',
parent: QObject = None) -> None:
super().__init__(parent) super().__init__(parent)
self.changed.connect(_render_stylesheet.cache_clear) self.changed.connect(_render_stylesheet.cache_clear)
self._mutables = {} # type: MutableMapping[str, Tuple[Any, Any]] self._mutables = {}
self._yaml = yaml_config self._yaml = yaml_config
self._init_values() self._init_values()
def _init_values(self) -> None: def _init_values(self):
"""Populate the self._values dict.""" """Populate the self._values dict."""
self._values = {} # type: typing.Mapping self._values = {}
for name, opt in configdata.DATA.items(): for name, opt in configdata.DATA.items():
self._values[name] = configutils.Values(opt) self._values[name] = configutils.Values(opt)
def __iter__(self) -> typing.Iterator[configutils.Values]: def __iter__(self):
"""Iterate over configutils.Values items.""" """Iterate over configutils.Values items."""
yield from self._values.values() yield from self._values.values()
def init_save_manager(self, def init_save_manager(self, save_manager):
save_manager: 'savemanager.SaveManager') -> None:
"""Make sure the config gets saved properly. """Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before We do this outside of __init__ because the config gets created before
@ -304,10 +279,7 @@ class Config(QObject):
""" """
self._yaml.init_save_manager(save_manager) self._yaml.init_save_manager(save_manager)
def _set_value(self, def _set_value(self, opt, value, pattern=None):
opt: 'configdata.Option',
value: Any,
pattern: urlmatch.UrlPattern = None) -> None:
"""Set the given option to the given value.""" """Set the given option to the given value."""
if not isinstance(objects.backend, objects.NoBackend): if not isinstance(objects.backend, objects.NoBackend):
if objects.backend not in opt.backends: if objects.backend not in opt.backends:
@ -322,12 +294,12 @@ class Config(QObject):
log.config.debug("Config option changed: {} = {}".format( log.config.debug("Config option changed: {} = {}".format(
opt.name, value)) opt.name, value))
def _check_yaml(self, opt: 'configdata.Option', save_yaml: bool) -> None: def _check_yaml(self, opt, save_yaml):
"""Make sure the given option may be set in autoconfig.yml.""" """Make sure the given option may be set in autoconfig.yml."""
if save_yaml and opt.no_autoconfig: if save_yaml and opt.no_autoconfig:
raise configexc.NoAutoconfigError(opt.name) raise configexc.NoAutoconfigError(opt.name)
def read_yaml(self) -> None: def read_yaml(self):
"""Read the YAML settings from self._yaml.""" """Read the YAML settings from self._yaml."""
self._yaml.load() self._yaml.load()
for values in self._yaml: for values in self._yaml:
@ -335,7 +307,7 @@ class Config(QObject):
self._set_value(values.opt, scoped.value, self._set_value(values.opt, scoped.value,
pattern=scoped.pattern) pattern=scoped.pattern)
def get_opt(self, name: str) -> 'configdata.Option': def get_opt(self, name):
"""Get a configdata.Option object for the given setting.""" """Get a configdata.Option object for the given setting."""
try: try:
return configdata.DATA[name] return configdata.DATA[name]
@ -346,10 +318,7 @@ class Config(QObject):
name, deleted=deleted, renamed=renamed) name, deleted=deleted, renamed=renamed)
raise exception from None raise exception from None
def get(self, def get(self, name, url=None, *, fallback=True):
name: str,
url: QUrl = None, *,
fallback: bool = True) -> Any:
"""Get the given setting converted for Python code. """Get the given setting converted for Python code.
Args: Args:
@ -359,7 +328,7 @@ class Config(QObject):
obj = self.get_obj(name, url=url, fallback=fallback) obj = self.get_obj(name, url=url, fallback=fallback)
return opt.typ.to_py(obj) return opt.typ.to_py(obj)
def _maybe_copy(self, value: Any) -> Any: def _maybe_copy(self, value):
"""Copy the value if it could potentially be mutated.""" """Copy the value if it could potentially be mutated."""
if isinstance(value, self.MUTABLE_TYPES): if isinstance(value, self.MUTABLE_TYPES):
# For mutable objects, create a copy so we don't accidentally # For mutable objects, create a copy so we don't accidentally
@ -370,10 +339,7 @@ class Config(QObject):
assert value.__hash__ is not None, value assert value.__hash__ is not None, value
return value return value
def get_obj(self, def get_obj(self, name, *, url=None, fallback=True):
name: str, *,
url: QUrl = None,
fallback: bool = True) -> Any:
"""Get the given setting as object (for YAML/config.py). """Get the given setting as object (for YAML/config.py).
Note that the returned values are not watched for mutation. Note that the returned values are not watched for mutation.
@ -383,10 +349,7 @@ class Config(QObject):
value = self._values[name].get_for_url(url, fallback=fallback) value = self._values[name].get_for_url(url, fallback=fallback)
return self._maybe_copy(value) return self._maybe_copy(value)
def get_obj_for_pattern( def get_obj_for_pattern(self, name, *, pattern):
self, name: str, *,
pattern: typing.Optional[urlmatch.UrlPattern]
) -> Any:
"""Get the given setting as object (for YAML/config.py). """Get the given setting as object (for YAML/config.py).
This gets the overridden value for a given pattern, or This gets the overridden value for a given pattern, or
@ -396,8 +359,7 @@ class Config(QObject):
value = self._values[name].get_for_pattern(pattern, fallback=False) value = self._values[name].get_for_pattern(pattern, fallback=False)
return self._maybe_copy(value) return self._maybe_copy(value)
def get_mutable_obj(self, name: str, *, def get_mutable_obj(self, name, *, pattern=None):
pattern: urlmatch.UrlPattern = None) -> Any:
"""Get an object which can be mutated, e.g. in a config.py. """Get an object which can be mutated, e.g. in a config.py.
If a pattern is given, return the value for that pattern. If a pattern is given, return the value for that pattern.
@ -422,8 +384,7 @@ class Config(QObject):
return copy_value return copy_value
def get_str(self, name: str, *, def get_str(self, name, *, pattern=None):
pattern: urlmatch.UrlPattern = None) -> str:
"""Get the given setting as string. """Get the given setting as string.
If a pattern is given, get the setting for the given pattern or If a pattern is given, get the setting for the given pattern or
@ -434,10 +395,7 @@ class Config(QObject):
value = values.get_for_pattern(pattern) value = values.get_for_pattern(pattern)
return opt.typ.to_str(value) return opt.typ.to_str(value)
def set_obj(self, name: str, def set_obj(self, name, value, *, pattern=None, save_yaml=False):
value: Any, *,
pattern: urlmatch.UrlPattern = None,
save_yaml: bool = False) -> None:
"""Set the given setting from a YAML/config.py object. """Set the given setting from a YAML/config.py object.
If save_yaml=True is given, store the new value to YAML. If save_yaml=True is given, store the new value to YAML.
@ -448,10 +406,7 @@ class Config(QObject):
if save_yaml: if save_yaml:
self._yaml.set_obj(name, value, pattern=pattern) self._yaml.set_obj(name, value, pattern=pattern)
def set_str(self, name: str, def set_str(self, name, value, *, pattern=None, save_yaml=False):
value: str, *,
pattern: urlmatch.UrlPattern = None,
save_yaml: bool = False) -> None:
"""Set the given setting from a string. """Set the given setting from a string.
If save_yaml=True is given, store the new value to YAML. If save_yaml=True is given, store the new value to YAML.
@ -466,9 +421,7 @@ class Config(QObject):
if save_yaml: if save_yaml:
self._yaml.set_obj(name, converted, pattern=pattern) self._yaml.set_obj(name, converted, pattern=pattern)
def unset(self, name: str, *, def unset(self, name, *, save_yaml=False, pattern=None):
save_yaml: bool = False,
pattern: urlmatch.UrlPattern = None) -> None:
"""Set the given setting back to its default.""" """Set the given setting back to its default."""
opt = self.get_opt(name) opt = self.get_opt(name)
self._check_yaml(opt, save_yaml) self._check_yaml(opt, save_yaml)
@ -479,7 +432,7 @@ class Config(QObject):
if save_yaml: if save_yaml:
self._yaml.unset(name, pattern=pattern) self._yaml.unset(name, pattern=pattern)
def clear(self, *, save_yaml: bool = False) -> None: def clear(self, *, save_yaml=False):
"""Clear all settings in the config. """Clear all settings in the config.
If save_yaml=True is given, also remove all customization from the YAML If save_yaml=True is given, also remove all customization from the YAML
@ -493,7 +446,7 @@ class Config(QObject):
if save_yaml: if save_yaml:
self._yaml.clear() self._yaml.clear()
def update_mutables(self, *, save_yaml: bool = False) -> None: def update_mutables(self, *, save_yaml=False):
"""Update mutable settings if they changed. """Update mutable settings if they changed.
Every time someone calls get_obj() on a mutable object, we save a Every time someone calls get_obj() on a mutable object, we save a
@ -508,7 +461,7 @@ class Config(QObject):
self.set_obj(name, new_value, save_yaml=save_yaml) self.set_obj(name, new_value, save_yaml=save_yaml)
self._mutables = {} self._mutables = {}
def dump_userconfig(self) -> str: def dump_userconfig(self):
"""Get the part of the config which was changed by the user. """Get the part of the config which was changed by the user.
Return: Return:
@ -537,10 +490,7 @@ class ConfigContainer:
_pattern: The URL pattern to be used. _pattern: The URL pattern to be used.
""" """
def __init__(self, config: Config, def __init__(self, config, configapi=None, prefix='', pattern=None):
configapi: 'configfiles.ConfigAPI' = None,
prefix: str = '',
pattern: urlmatch.UrlPattern = None) -> None:
self._config = config self._config = config
self._prefix = prefix self._prefix = prefix
self._configapi = configapi self._configapi = configapi
@ -548,13 +498,13 @@ class ConfigContainer:
if configapi is None and pattern is not None: if configapi is None and pattern is not None:
raise TypeError("Can't use pattern without configapi!") raise TypeError("Can't use pattern without configapi!")
def __repr__(self) -> str: def __repr__(self):
return utils.get_repr(self, constructor=True, config=self._config, 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) pattern=self._pattern)
@contextlib.contextmanager @contextlib.contextmanager
def _handle_error(self, action: str, name: str) -> typing.Iterator[None]: def _handle_error(self, action, name):
try: try:
yield yield
except configexc.Error as e: except configexc.Error as e:
@ -563,7 +513,7 @@ class ConfigContainer:
text = "While {} '{}'".format(action, name) text = "While {} '{}'".format(action, name)
self._configapi.errors.append(configexc.ConfigErrorDesc(text, e)) self._configapi.errors.append(configexc.ConfigErrorDesc(text, e))
def __getattr__(self, attr: str) -> Any: def __getattr__(self, attr):
"""Get an option or a new ConfigContainer with the added prefix. """Get an option or a new ConfigContainer with the added prefix.
If we get an option which exists, we return the value for it. If we get an option which exists, we return the value for it.
@ -590,7 +540,7 @@ class ConfigContainer:
return self._config.get_mutable_obj( return self._config.get_mutable_obj(
name, pattern=self._pattern) name, pattern=self._pattern)
def __setattr__(self, attr: str, value: Any) -> None: def __setattr__(self, attr, value):
"""Set the given option in the config.""" """Set the given option in the config."""
if attr.startswith('_'): if attr.startswith('_'):
super().__setattr__(attr, value) super().__setattr__(attr, value)
@ -600,7 +550,7 @@ class ConfigContainer:
with self._handle_error('setting', name): with self._handle_error('setting', name):
self._config.set_obj(name, value, pattern=self._pattern) self._config.set_obj(name, value, pattern=self._pattern)
def _join(self, attr: str) -> str: def _join(self, attr):
"""Get the prefix joined with the given attribute.""" """Get the prefix joined with the given attribute."""
if self._prefix: if self._prefix:
return '{}.{}'.format(self._prefix, attr) return '{}.{}'.format(self._prefix, attr)
@ -608,10 +558,8 @@ class ConfigContainer:
return attr return attr
def set_register_stylesheet(obj: QObject, *, def set_register_stylesheet(obj, *, stylesheet=None, update=True):
stylesheet: str = None, """Set the stylesheet for an object based on it's STYLESHEET attribute.
update: bool = True) -> None:
"""Set the stylesheet for an object.
Also, register an update when the config is changed. Also, register an update when the config is changed.
@ -626,7 +574,7 @@ def set_register_stylesheet(obj: QObject, *,
@functools.lru_cache() @functools.lru_cache()
def _render_stylesheet(stylesheet: str) -> str: def _render_stylesheet(stylesheet):
"""Render the given stylesheet jinja template.""" """Render the given stylesheet jinja template."""
with jinja.environment.no_autoescape(): with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet) template = jinja.environment.from_string(stylesheet)
@ -642,9 +590,7 @@ class StyleSheetObserver(QObject):
_stylesheet: The stylesheet template to use. _stylesheet: The stylesheet template to use.
""" """
def __init__(self, obj: QObject, def __init__(self, obj, stylesheet, update):
stylesheet: typing.Optional[str],
update: bool) -> None:
super().__init__() super().__init__()
self._obj = obj self._obj = obj
self._update = update self._update = update
@ -653,11 +599,11 @@ class StyleSheetObserver(QObject):
if self._update: if self._update:
self.setParent(self._obj) self.setParent(self._obj)
if stylesheet is None: if stylesheet is None:
self._stylesheet = obj.STYLESHEET # type: str self._stylesheet = obj.STYLESHEET
else: else:
self._stylesheet = stylesheet self._stylesheet = stylesheet
def _get_stylesheet(self) -> str: def _get_stylesheet(self):
"""Format a stylesheet based on a template. """Format a stylesheet based on a template.
Return: Return:
@ -666,15 +612,19 @@ class StyleSheetObserver(QObject):
return _render_stylesheet(self._stylesheet) return _render_stylesheet(self._stylesheet)
@pyqtSlot() @pyqtSlot()
def _update_stylesheet(self) -> None: def _update_stylesheet(self):
"""Update the stylesheet for obj.""" """Update the stylesheet for obj."""
self._obj.setStyleSheet(self._get_stylesheet()) self._obj.setStyleSheet(self._get_stylesheet())
def register(self) -> None: def register(self):
"""Do a first update and listen for more.""" """Do a first update and listen for more.
Args:
update: if False, don't listen for future updates.
"""
qss = self._get_stylesheet() qss = self._get_stylesheet()
log.config.vdebug( # type: ignore log.config.vdebug("stylesheet for {}: {}".format(
"stylesheet for {}: {}".format(self._obj.__class__.__name__, qss)) self._obj.__class__.__name__, qss))
self._obj.setStyleSheet(qss) self._obj.setStyleSheet(qss)
if self._update: if self._update:
instance.changed.connect(self._update_stylesheet) instance.changed.connect(self._update_stylesheet)

View File

@ -19,7 +19,6 @@
"""Commands related to the configuration.""" """Commands related to the configuration."""
import typing
import os.path import os.path
import contextlib import contextlib
@ -32,34 +31,24 @@ from qutebrowser.config import configtypes, configexc, configfiles, configdata
from qutebrowser.misc import editor from qutebrowser.misc import editor
from qutebrowser.keyinput import keyutils from qutebrowser.keyinput import keyutils
MYPY = False
if MYPY: # pragma: no cover
# pylint: disable=unused-import,useless-suppression
from qutebrowser.config.config import Config, KeyConfig
class ConfigCommands: class ConfigCommands:
"""qutebrowser commands related to the configuration.""" """qutebrowser commands related to the configuration."""
def __init__(self, def __init__(self, config, keyconfig):
config: 'Config',
keyconfig: 'KeyConfig') -> None:
self._config = config self._config = config
self._keyconfig = keyconfig self._keyconfig = keyconfig
@contextlib.contextmanager @contextlib.contextmanager
def _handle_config_error(self) -> typing.Iterator[None]: def _handle_config_error(self):
"""Catch errors in set_command and raise CommandError.""" """Catch errors in set_command and raise CommandError."""
try: try:
yield yield
except configexc.Error as e: except configexc.Error as e:
raise cmdutils.CommandError(str(e)) raise cmdutils.CommandError(str(e))
def _parse_pattern( def _parse_pattern(self, pattern):
self,
pattern: typing.Optional[str]
) -> typing.Optional[urlmatch.UrlPattern]:
"""Parse a pattern string argument to a pattern.""" """Parse a pattern string argument to a pattern."""
if pattern is None: if pattern is None:
return None return None
@ -70,15 +59,14 @@ class ConfigCommands:
raise cmdutils.CommandError("Error while parsing {}: {}" raise cmdutils.CommandError("Error while parsing {}: {}"
.format(pattern, str(e))) .format(pattern, str(e)))
def _parse_key(self, key: str) -> keyutils.KeySequence: def _parse_key(self, key):
"""Parse a key argument.""" """Parse a key argument."""
try: try:
return keyutils.KeySequence.parse(key) return keyutils.KeySequence.parse(key)
except keyutils.KeyParseError as e: except keyutils.KeyParseError as e:
raise cmdutils.CommandError(str(e)) raise cmdutils.CommandError(str(e))
def _print_value(self, option: str, def _print_value(self, option, pattern):
pattern: typing.Optional[urlmatch.UrlPattern]) -> None:
"""Print the value of the given option.""" """Print the value of the given option."""
with self._handle_config_error(): with self._handle_config_error():
value = self._config.get_str(option, pattern=pattern) value = self._config.get_str(option, pattern=pattern)
@ -93,9 +81,8 @@ class ConfigCommands:
@cmdutils.argument('value', completion=configmodel.value) @cmdutils.argument('value', completion=configmodel.value)
@cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('win_id', value=cmdutils.Value.win_id)
@cmdutils.argument('pattern', flag='u') @cmdutils.argument('pattern', flag='u')
def set(self, win_id: int, option: str = None, value: str = None, def set(self, win_id, option=None, value=None, temp=False, print_=False,
temp: bool = False, print_: bool = False, *, pattern=None):
*, pattern: str = None) -> None:
"""Set an option. """Set an option.
If the option name ends with '?' or no value is provided, the If the option name ends with '?' or no value is provided, the
@ -121,28 +108,28 @@ class ConfigCommands:
raise cmdutils.CommandError("Toggling values was moved to the " raise cmdutils.CommandError("Toggling values was moved to the "
":config-cycle command") ":config-cycle command")
parsed_pattern = self._parse_pattern(pattern) pattern = self._parse_pattern(pattern)
if option.endswith('?') and option != '?': if option.endswith('?') and option != '?':
self._print_value(option[:-1], pattern=parsed_pattern) self._print_value(option[:-1], pattern=pattern)
return return
with self._handle_config_error(): with self._handle_config_error():
if value is None: if value is None:
self._print_value(option, pattern=parsed_pattern) self._print_value(option, pattern=pattern)
else: else:
self._config.set_str(option, value, pattern=parsed_pattern, self._config.set_str(option, value, pattern=pattern,
save_yaml=not temp) save_yaml=not temp)
if print_: if print_:
self._print_value(option, pattern=parsed_pattern) self._print_value(option, pattern=pattern)
@cmdutils.register(instance='config-commands', maxsplit=1, @cmdutils.register(instance='config-commands', maxsplit=1,
no_cmd_split=True, no_replace_variables=True) no_cmd_split=True, no_replace_variables=True)
@cmdutils.argument('command', completion=configmodel.bind) @cmdutils.argument('command', completion=configmodel.bind)
@cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('win_id', value=cmdutils.Value.win_id)
def bind(self, win_id: str, key: str = None, command: str = None, *, def bind(self, win_id, key=None, command=None, *, mode='normal',
mode: str = 'normal', default: bool = False) -> None: default=False):
"""Bind a key to a command. """Bind a key to a command.
If no command is given, show the current binding for the given key. If no command is given, show the current binding for the given key.
@ -187,7 +174,7 @@ class ConfigCommands:
self._keyconfig.bind(seq, command, mode=mode, save_yaml=True) self._keyconfig.bind(seq, command, mode=mode, save_yaml=True)
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
def unbind(self, key: str, *, mode: str = 'normal') -> None: def unbind(self, key, *, mode='normal'):
"""Unbind a keychain. """Unbind a keychain.
Args: Args:
@ -204,9 +191,8 @@ class ConfigCommands:
@cmdutils.argument('option', completion=configmodel.option) @cmdutils.argument('option', completion=configmodel.option)
@cmdutils.argument('values', completion=configmodel.value) @cmdutils.argument('values', completion=configmodel.value)
@cmdutils.argument('pattern', flag='u') @cmdutils.argument('pattern', flag='u')
def config_cycle(self, option: str, *values: str, def config_cycle(self, option, *values, pattern=None, temp=False,
pattern: str = None, print_=False):
temp: bool = False, print_: bool = False) -> None:
"""Cycle an option between multiple values. """Cycle an option between multiple values.
Args: Args:
@ -216,15 +202,15 @@ class ConfigCommands:
temp: Set value temporarily until qutebrowser is closed. temp: Set value temporarily until qutebrowser is closed.
print_: Print the value after setting. print_: Print the value after setting.
""" """
parsed_pattern = self._parse_pattern(pattern) pattern = self._parse_pattern(pattern)
with self._handle_config_error(): with self._handle_config_error():
opt = self._config.get_opt(option) opt = self._config.get_opt(option)
old_value = self._config.get_obj_for_pattern( old_value = self._config.get_obj_for_pattern(option,
option, pattern=parsed_pattern) pattern=pattern)
if not values and isinstance(opt.typ, configtypes.Bool): if not values and isinstance(opt.typ, configtypes.Bool):
values = ('true', 'false') values = ['true', 'false']
if len(values) < 2: if len(values) < 2:
raise cmdutils.CommandError("Need at least two values for " raise cmdutils.CommandError("Need at least two values for "
@ -233,25 +219,25 @@ class ConfigCommands:
# Use the next valid value from values, or the first if the current # Use the next valid value from values, or the first if the current
# value does not appear in the list # value does not appear in the list
with self._handle_config_error(): with self._handle_config_error():
cycle_values = [opt.typ.from_str(val) for val in values] values = [opt.typ.from_str(val) for val in values]
try: try:
idx = cycle_values.index(old_value) idx = values.index(old_value)
idx = (idx + 1) % len(cycle_values) idx = (idx + 1) % len(values)
value = cycle_values[idx] value = values[idx]
except ValueError: except ValueError:
value = cycle_values[0] value = values[0]
with self._handle_config_error(): with self._handle_config_error():
self._config.set_obj(option, value, pattern=parsed_pattern, self._config.set_obj(option, value, pattern=pattern,
save_yaml=not temp) save_yaml=not temp)
if print_: if print_:
self._print_value(option, pattern=parsed_pattern) self._print_value(option, pattern=pattern)
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.customized_option) @cmdutils.argument('option', completion=configmodel.customized_option)
def config_unset(self, option: str, temp: bool = False) -> None: def config_unset(self, option, temp=False):
"""Unset an option. """Unset an option.
This sets an option back to its default and removes it from This sets an option back to its default and removes it from
@ -266,8 +252,7 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.list_option) @cmdutils.argument('option', completion=configmodel.list_option)
def config_list_add(self, option: str, value: str, def config_list_add(self, option, value, temp=False):
temp: bool = False) -> None:
"""Append a value to a config option that is a list. """Append a value to a config option that is a list.
Args: Args:
@ -288,8 +273,7 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.dict_option) @cmdutils.argument('option', completion=configmodel.dict_option)
def config_dict_add(self, option: str, key: str, value: str, def config_dict_add(self, option, key, value, temp=False, replace=False):
temp: bool = False, replace: bool = False) -> None:
"""Add a key/value pair to a dictionary option. """Add a key/value pair to a dictionary option.
Args: Args:
@ -318,8 +302,7 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.list_option) @cmdutils.argument('option', completion=configmodel.list_option)
def config_list_remove(self, option: str, value: str, def config_list_remove(self, option, value, temp=False):
temp: bool = False) -> None:
"""Remove a value from a list. """Remove a value from a list.
Args: Args:
@ -346,8 +329,7 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.dict_option) @cmdutils.argument('option', completion=configmodel.dict_option)
def config_dict_remove(self, option: str, key: str, def config_dict_remove(self, option, key, temp=False):
temp: bool = False) -> None:
"""Remove a key from a dict. """Remove a key from a dict.
Args: Args:
@ -372,7 +354,7 @@ class ConfigCommands:
self._config.update_mutables(save_yaml=not temp) self._config.update_mutables(save_yaml=not temp)
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
def config_clear(self, save: bool = False) -> None: def config_clear(self, save=False):
"""Set all settings back to their default. """Set all settings back to their default.
Args: Args:
@ -382,7 +364,7 @@ class ConfigCommands:
self._config.clear(save_yaml=save) self._config.clear(save_yaml=save)
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
def config_source(self, filename: str = None, clear: bool = False) -> None: def config_source(self, filename=None, clear=False):
"""Read a config.py file. """Read a config.py file.
Args: Args:
@ -404,13 +386,13 @@ class ConfigCommands:
raise cmdutils.CommandError(e) raise cmdutils.CommandError(e)
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
def config_edit(self, no_source: bool = False) -> None: def config_edit(self, no_source=False):
"""Open the config.py file in the editor. """Open the config.py file in the editor.
Args: Args:
no_source: Don't re-source the config file after editing. no_source: Don't re-source the config file after editing.
""" """
def on_file_updated() -> None: def on_file_updated():
"""Source the new config when editing finished. """Source the new config when editing finished.
This can't use cmdutils.CommandError as it's run async. This can't use cmdutils.CommandError as it's run async.
@ -428,8 +410,7 @@ class ConfigCommands:
ed.edit_file(filename) ed.edit_file(filename)
@cmdutils.register(instance='config-commands') @cmdutils.register(instance='config-commands')
def config_write_py(self, filename: str = None, def config_write_py(self, filename=None, force=False, defaults=False):
force: bool = False, defaults: bool = False) -> None:
"""Write the current configuration to a config.py file. """Write the current configuration to a config.py file.
Args: Args:
@ -448,13 +429,13 @@ class ConfigCommands:
raise cmdutils.CommandError("{} already exists - use --force to " raise cmdutils.CommandError("{} already exists - use --force to "
"overwrite!".format(filename)) "overwrite!".format(filename))
options = [] # type: typing.List
if defaults: if defaults:
options = [(None, opt, opt.default) options = [(None, opt, opt.default)
for _name, opt in sorted(configdata.DATA.items())] for _name, opt in sorted(configdata.DATA.items())]
bindings = dict(configdata.DATA['bindings.default'].default) bindings = dict(configdata.DATA['bindings.default'].default)
commented = True commented = True
else: else:
options = []
for values in self._config: for values in self._config:
for scoped in values: for scoped in values:
options.append((scoped.pattern, values.opt, scoped.value)) options.append((scoped.pattern, values.opt, scoped.value))

View File

@ -24,18 +24,14 @@ Module attributes:
DATA: A dict of Option objects after init() has been called. DATA: A dict of Option objects after init() has been called.
""" """
import typing
from typing import Optional # pylint: disable=unused-import
import functools import functools
import attr import attr
from qutebrowser.config import configtypes from qutebrowser.config import configtypes
from qutebrowser.utils import usertypes, qtutils, utils from qutebrowser.utils import usertypes, qtutils, utils
DATA = typing.cast(typing.Mapping[str, 'Option'], None) DATA = None
MIGRATIONS = typing.cast('Migrations', None) MIGRATIONS = None
_BackendDict = typing.Mapping[str, typing.Union[str, bool]]
@attr.s @attr.s
@ -46,15 +42,15 @@ class Option:
Note that this is just an option which exists, with no value associated. Note that this is just an option which exists, with no value associated.
""" """
name = attr.ib() # type: str name = attr.ib()
typ = attr.ib() # type: configtypes.BaseType typ = attr.ib()
default = attr.ib() # type: typing.Any default = attr.ib()
backends = attr.ib() # type: typing.Iterable[usertypes.Backend] backends = attr.ib()
raw_backends = attr.ib() # type: Optional[typing.Mapping[str, bool]] raw_backends = attr.ib()
description = attr.ib() # type: str description = attr.ib()
supports_pattern = attr.ib(default=False) # type: bool supports_pattern = attr.ib(default=False)
restart = attr.ib(default=False) # type: bool restart = attr.ib(default=False)
no_autoconfig = attr.ib(default=False) # type: bool no_autoconfig = attr.ib(default=False)
@attr.s @attr.s
@ -67,13 +63,11 @@ class Migrations:
deleted: A list of option names which have been removed. deleted: A list of option names which have been removed.
""" """
renamed = attr.ib( renamed = attr.ib(default=attr.Factory(dict))
default=attr.Factory(dict)) # type: typing.Dict[str, str] deleted = attr.ib(default=attr.Factory(list))
deleted = attr.ib(
default=attr.Factory(list)) # type: typing.List[str]
def _raise_invalid_node(name: str, what: str, node: typing.Any) -> None: def _raise_invalid_node(name, what, node):
"""Raise an exception for an invalid configdata YAML node. """Raise an exception for an invalid configdata YAML node.
Args: Args:
@ -85,16 +79,13 @@ def _raise_invalid_node(name: str, what: str, node: typing.Any) -> None:
name, what, node)) name, what, node))
def _parse_yaml_type( def _parse_yaml_type(name, node):
name: str,
node: typing.Union[str, typing.Mapping[str, typing.Any]],
) -> configtypes.BaseType:
if isinstance(node, str): if isinstance(node, str):
# e.g: # e.g:
# type: Bool # type: Bool
# -> create the type object without any arguments # -> create the type object without any arguments
type_name = node type_name = node
kwargs = {} # type: typing.MutableMapping[str, typing.Any] kwargs = {}
elif isinstance(node, dict): elif isinstance(node, dict):
# e.g: # e.g:
# type: # type:
@ -132,10 +123,7 @@ def _parse_yaml_type(
type_name, node, e)) type_name, node, e))
def _parse_yaml_backends_dict( def _parse_yaml_backends_dict(name, node):
name: str,
node: _BackendDict,
) -> typing.Sequence[usertypes.Backend]:
"""Parse a dict definition for backends. """Parse a dict definition for backends.
Example: Example:
@ -172,10 +160,7 @@ def _parse_yaml_backends_dict(
return backends return backends
def _parse_yaml_backends( def _parse_yaml_backends(name, node):
name: str,
node: typing.Union[None, str, _BackendDict],
) -> typing.Sequence[usertypes.Backend]:
"""Parse a backend node in the yaml. """Parse a backend node in the yaml.
It can have one of those four forms: It can have one of those four forms:
@ -202,9 +187,7 @@ def _parse_yaml_backends(
raise utils.Unreachable raise utils.Unreachable
def _read_yaml( def _read_yaml(yaml_data):
yaml_data: str,
) -> typing.Tuple[typing.Mapping[str, Option], Migrations]:
"""Read config data from a YAML file. """Read config data from a YAML file.
Args: Args:
@ -266,12 +249,12 @@ def _read_yaml(
@functools.lru_cache(maxsize=256) @functools.lru_cache(maxsize=256)
def is_valid_prefix(prefix: str) -> bool: def is_valid_prefix(prefix):
"""Check whether the given prefix is a valid prefix for some option.""" """Check whether the given prefix is a valid prefix for some option."""
return any(key.startswith(prefix + '.') for key in DATA) return any(key.startswith(prefix + '.') for key in DATA)
def init() -> None: def init():
"""Initialize configdata from the YAML file.""" """Initialize configdata from the YAML file."""
global DATA, MIGRATIONS global DATA, MIGRATIONS
DATA, MIGRATIONS = _read_yaml(utils.read_file('config/configdata.yml')) DATA, MIGRATIONS = _read_yaml(utils.read_file('config/configdata.yml'))

View File

@ -19,7 +19,6 @@
"""Code to show a diff of the legacy config format.""" """Code to show a diff of the legacy config format."""
import typing # pylint: disable=unused-import,useless-suppression
import difflib import difflib
import os.path import os.path
@ -728,10 +727,10 @@ scroll right
""" """
def get_diff() -> str: def get_diff():
"""Get a HTML diff for the old config files.""" """Get a HTML diff for the old config files."""
old_conf_lines = [] # type: typing.MutableSequence[str] old_conf_lines = []
old_key_lines = [] # type: typing.MutableSequence[str] old_key_lines = []
for filename, dest in [('qutebrowser.conf', old_conf_lines), for filename, dest in [('qutebrowser.conf', old_conf_lines),
('keys.conf', old_key_lines)]: ('keys.conf', old_key_lines)]:

View File

@ -19,10 +19,9 @@
"""Exceptions related to config parsing.""" """Exceptions related to config parsing."""
import typing
import attr import attr
from qutebrowser.utils import jinja, usertypes from qutebrowser.utils import jinja
class Error(Exception): class Error(Exception):
@ -34,7 +33,7 @@ class NoAutoconfigError(Error):
"""Raised when this option can't be set in autoconfig.yml.""" """Raised when this option can't be set in autoconfig.yml."""
def __init__(self, name: str) -> None: def __init__(self, name):
super().__init__("The {} setting can only be set in config.py!" super().__init__("The {} setting can only be set in config.py!"
.format(name)) .format(name))
@ -43,11 +42,7 @@ class BackendError(Error):
"""Raised when this setting is unavailable with the current backend.""" """Raised when this setting is unavailable with the current backend."""
def __init__( def __init__(self, name, backend, raw_backends):
self, name: str,
backend: usertypes.Backend,
raw_backends: typing.Optional[typing.Mapping[str, bool]]
) -> None:
if raw_backends is None or not raw_backends[backend.name]: if raw_backends is None or not raw_backends[backend.name]:
msg = ("The {} setting is not available with the {} backend!" msg = ("The {} setting is not available with the {} backend!"
.format(name, backend.name)) .format(name, backend.name))
@ -62,7 +57,7 @@ class NoPatternError(Error):
"""Raised when the given setting does not support URL patterns.""" """Raised when the given setting does not support URL patterns."""
def __init__(self, name: str) -> None: def __init__(self, name):
super().__init__("The {} setting does not support URL patterns!" super().__init__("The {} setting does not support URL patterns!"
.format(name)) .format(name))
@ -76,7 +71,7 @@ class ValidationError(Error):
msg: Additional error message. msg: Additional error message.
""" """
def __init__(self, value: typing.Any, msg: str) -> None: def __init__(self, value, msg):
super().__init__("Invalid value '{}' - {}".format(value, msg)) super().__init__("Invalid value '{}' - {}".format(value, msg))
self.option = None self.option = None
@ -90,9 +85,7 @@ class NoOptionError(Error):
"""Raised when an option was not found.""" """Raised when an option was not found."""
def __init__(self, option: str, *, def __init__(self, option, *, deleted=False, renamed=None):
deleted: bool = False,
renamed: str = None) -> None:
if deleted: if deleted:
assert renamed is None assert renamed is None
suffix = ' (this option was removed from qutebrowser)' suffix = ' (this option was removed from qutebrowser)'
@ -116,18 +109,18 @@ class ConfigErrorDesc:
traceback: The formatted traceback of the exception. traceback: The formatted traceback of the exception.
""" """
text = attr.ib() # type: str text = attr.ib()
exception = attr.ib() # type: typing.Union[str, Exception] exception = attr.ib()
traceback = attr.ib(None) # type: str traceback = attr.ib(None)
def __str__(self) -> str: def __str__(self):
if self.traceback: if self.traceback:
return '{} - {}: {}'.format(self.text, return '{} - {}: {}'.format(self.text,
self.exception.__class__.__name__, self.exception.__class__.__name__,
self.exception) self.exception)
return '{}: {}'.format(self.text, self.exception) return '{}: {}'.format(self.text, self.exception)
def with_text(self, text: str) -> 'ConfigErrorDesc': def with_text(self, text):
"""Get a new ConfigErrorDesc with the given text appended.""" """Get a new ConfigErrorDesc with the given text appended."""
return self.__class__(text='{} ({})'.format(self.text, text), return self.__class__(text='{} ({})'.format(self.text, text),
exception=self.exception, exception=self.exception,
@ -138,15 +131,13 @@ class ConfigFileErrors(Error):
"""Raised when multiple errors occurred inside the config.""" """Raised when multiple errors occurred inside the config."""
def __init__(self, def __init__(self, basename, errors):
basename: str,
errors: typing.Sequence[ConfigErrorDesc]) -> None:
super().__init__("Errors occurred while reading {}:\n{}".format( super().__init__("Errors occurred while reading {}:\n{}".format(
basename, '\n'.join(' {}'.format(e) for e in errors))) basename, '\n'.join(' {}'.format(e) for e in errors)))
self.basename = basename self.basename = basename
self.errors = errors self.errors = errors
def to_html(self) -> str: def to_html(self):
"""Get the error texts as a HTML snippet.""" """Get the error texts as a HTML snippet."""
template = jinja.environment.from_string(""" template = jinja.environment.from_string("""
Errors occurred while reading {{ basename }}: Errors occurred while reading {{ basename }}:

View File

@ -27,7 +27,6 @@ import textwrap
import traceback import traceback
import configparser import configparser
import contextlib import contextlib
import typing
import yaml import yaml
from PyQt5.QtCore import pyqtSignal, QObject, QSettings from PyQt5.QtCore import pyqtSignal, QObject, QSettings
@ -37,21 +36,16 @@ from qutebrowser.config import configexc, config, configdata, configutils
from qutebrowser.keyinput import keyutils from qutebrowser.keyinput import keyutils
from qutebrowser.utils import standarddir, utils, qtutils, log, urlmatch from qutebrowser.utils import standarddir, utils, qtutils, log, urlmatch
MYPY = False
if MYPY:
# pylint: disable=unused-import, useless-suppression
from qutebrowser.misc import savemanager
# The StateConfig instance # The StateConfig instance
state = typing.cast('StateConfig', None) state = None
class StateConfig(configparser.ConfigParser): class StateConfig(configparser.ConfigParser):
"""The "state" file saving various application state.""" """The "state" file saving various application state."""
def __init__(self) -> None: def __init__(self):
super().__init__() super().__init__()
self._filename = os.path.join(standarddir.data(), 'state') self._filename = os.path.join(standarddir.data(), 'state')
self.read(self._filename, encoding='utf-8') self.read(self._filename, encoding='utf-8')
@ -65,8 +59,7 @@ class StateConfig(configparser.ConfigParser):
for key in deleted_keys: for key in deleted_keys:
self['general'].pop(key, None) self['general'].pop(key, None)
def init_save_manager(self, def init_save_manager(self, save_manager):
save_manager: 'savemanager.SaveManager') -> None:
"""Make sure the config gets saved properly. """Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before We do this outside of __init__ because the config gets created before
@ -74,7 +67,7 @@ class StateConfig(configparser.ConfigParser):
""" """
save_manager.add_saveable('state-config', self._save) save_manager.add_saveable('state-config', self._save)
def _save(self) -> None: def _save(self):
"""Save the state file to the configured location.""" """Save the state file to the configured location."""
with open(self._filename, 'w', encoding='utf-8') as f: with open(self._filename, 'w', encoding='utf-8') as f:
self.write(f) self.write(f)
@ -91,20 +84,17 @@ class YamlConfig(QObject):
VERSION = 2 VERSION = 2
changed = pyqtSignal() changed = pyqtSignal()
_SettingsType = typing.Dict[str, typing.Dict[str, typing.Any]] def __init__(self, parent=None):
def __init__(self, parent: QObject = None) -> None:
super().__init__(parent) super().__init__(parent)
self._filename = os.path.join(standarddir.config(auto=True), self._filename = os.path.join(standarddir.config(auto=True),
'autoconfig.yml') 'autoconfig.yml')
self._dirty = False self._dirty = None
self._values = {} # type: typing.Dict[str, configutils.Values] self._values = {}
for name, opt in configdata.DATA.items(): for name, opt in configdata.DATA.items():
self._values[name] = configutils.Values(opt) self._values[name] = configutils.Values(opt)
def init_save_manager(self, def init_save_manager(self, save_manager):
save_manager: 'savemanager.SaveManager') -> None:
"""Make sure the config gets saved properly. """Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before We do this outside of __init__ because the config gets created before
@ -112,21 +102,21 @@ class YamlConfig(QObject):
""" """
save_manager.add_saveable('yaml-config', self._save, self.changed) save_manager.add_saveable('yaml-config', self._save, self.changed)
def __iter__(self) -> typing.Iterator[configutils.Values]: def __iter__(self):
"""Iterate over configutils.Values items.""" """Iterate over configutils.Values items."""
yield from self._values.values() yield from self._values.values()
def _mark_changed(self) -> None: def _mark_changed(self):
"""Mark the YAML config as changed.""" """Mark the YAML config as changed."""
self._dirty = True self._dirty = True
self.changed.emit() self.changed.emit()
def _save(self) -> None: def _save(self):
"""Save the settings to the YAML file if they've changed.""" """Save the settings to the YAML file if they've changed."""
if not self._dirty: if not self._dirty:
return return
settings = {} # type: YamlConfig._SettingsType settings = {}
for name, values in sorted(self._values.items()): for name, values in sorted(self._values.items()):
if not values: if not values:
continue continue
@ -145,10 +135,7 @@ class YamlConfig(QObject):
""".lstrip('\n'))) """.lstrip('\n')))
utils.yaml_dump(data, f) utils.yaml_dump(data, f)
def _pop_object(self, def _pop_object(self, yaml_data, key, typ):
yaml_data: typing.Any,
key: str,
typ: type) -> typing.Any:
"""Get a global object from the given data.""" """Get a global object from the given data."""
if not isinstance(yaml_data, dict): if not isinstance(yaml_data, dict):
desc = configexc.ConfigErrorDesc("While loading data", desc = configexc.ConfigErrorDesc("While loading data",
@ -171,7 +158,7 @@ class YamlConfig(QObject):
return data return data
def load(self) -> None: def load(self):
"""Load configuration from the configured YAML file.""" """Load configuration from the configured YAML file."""
try: try:
with open(self._filename, 'r', encoding='utf-8') as f: with open(self._filename, 'r', encoding='utf-8') as f:
@ -202,19 +189,18 @@ class YamlConfig(QObject):
self._validate(settings) self._validate(settings)
self._build_values(settings) self._build_values(settings)
def _load_settings_object(self, yaml_data: typing.Any) -> '_SettingsType': def _load_settings_object(self, yaml_data):
"""Load the settings from the settings: key.""" """Load the settings from the settings: key."""
return self._pop_object(yaml_data, 'settings', dict) return self._pop_object(yaml_data, 'settings', dict)
def _load_legacy_settings_object(self, def _load_legacy_settings_object(self, yaml_data):
yaml_data: typing.Any) -> '_SettingsType':
data = self._pop_object(yaml_data, 'global', dict) data = self._pop_object(yaml_data, 'global', dict)
settings = {} settings = {}
for name, value in data.items(): for name, value in data.items():
settings[name] = {'global': value} settings[name] = {'global': value}
return settings return settings
def _build_values(self, settings: typing.Mapping) -> None: def _build_values(self, settings):
"""Build up self._values from the values in the given dict.""" """Build up self._values from the values in the given dict."""
errors = [] errors = []
for name, yaml_values in settings.items(): for name, yaml_values in settings.items():
@ -247,8 +233,7 @@ class YamlConfig(QObject):
if errors: if errors:
raise configexc.ConfigFileErrors('autoconfig.yml', errors) raise configexc.ConfigFileErrors('autoconfig.yml', errors)
def _migrate_bool(self, settings: _SettingsType, name: str, def _migrate_bool(self, settings, name, true_value, false_value):
true_value: str, false_value: str) -> None:
"""Migrate a boolean in the settings.""" """Migrate a boolean in the settings."""
if name in settings: if name in settings:
for scope, val in settings[name].items(): for scope, val in settings[name].items():
@ -256,7 +241,7 @@ class YamlConfig(QObject):
settings[name][scope] = true_value if val else false_value settings[name][scope] = true_value if val else false_value
self._mark_changed() self._mark_changed()
def _handle_migrations(self, settings: _SettingsType) -> '_SettingsType': def _handle_migrations(self, settings):
"""Migrate older configs to the newest format.""" """Migrate older configs to the newest format."""
# Simple renamed/deleted options # Simple renamed/deleted options
for name in list(settings): for name in list(settings):
@ -314,7 +299,7 @@ class YamlConfig(QObject):
return settings return settings
def _validate(self, settings: _SettingsType) -> None: def _validate(self, settings):
"""Make sure all settings exist.""" """Make sure all settings exist."""
unknown = [] unknown = []
for name in settings: for name in settings:
@ -327,19 +312,18 @@ class YamlConfig(QObject):
for e in sorted(unknown)] for e in sorted(unknown)]
raise configexc.ConfigFileErrors('autoconfig.yml', errors) raise configexc.ConfigFileErrors('autoconfig.yml', errors)
def set_obj(self, name: str, value: typing.Any, *, def set_obj(self, name, value, *, pattern=None):
pattern: urlmatch.UrlPattern = None) -> None:
"""Set the given setting to the given value.""" """Set the given setting to the given value."""
self._values[name].add(value, pattern) self._values[name].add(value, pattern)
self._mark_changed() self._mark_changed()
def unset(self, name: str, *, pattern: urlmatch.UrlPattern = None) -> None: def unset(self, name, *, pattern=None):
"""Remove the given option name if it's configured.""" """Remove the given option name if it's configured."""
changed = self._values[name].remove(pattern) changed = self._values[name].remove(pattern)
if changed: if changed:
self._mark_changed() self._mark_changed()
def clear(self) -> None: def clear(self):
"""Clear all values from the YAML file.""" """Clear all values from the YAML file."""
for values in self._values.values(): for values in self._values.values():
values.clear() values.clear()
@ -362,15 +346,15 @@ class ConfigAPI:
datadir: The qutebrowser data directory, as pathlib.Path. datadir: The qutebrowser data directory, as pathlib.Path.
""" """
def __init__(self, conf: config.Config, keyconfig: config.KeyConfig): def __init__(self, conf, keyconfig):
self._config = conf self._config = conf
self._keyconfig = keyconfig self._keyconfig = keyconfig
self.errors = [] # type: typing.List[configexc.ConfigErrorDesc] self.errors = []
self.configdir = pathlib.Path(standarddir.config()) self.configdir = pathlib.Path(standarddir.config())
self.datadir = pathlib.Path(standarddir.data()) self.datadir = pathlib.Path(standarddir.data())
@contextlib.contextmanager @contextlib.contextmanager
def _handle_error(self, action: str, name: str) -> typing.Iterator[None]: def _handle_error(self, action, name):
"""Catch config-related exceptions and save them in self.errors.""" """Catch config-related exceptions and save them in self.errors."""
try: try:
yield yield
@ -388,40 +372,40 @@ class ConfigAPI:
text = "While {} '{}' and parsing key".format(action, name) text = "While {} '{}' and parsing key".format(action, name)
self.errors.append(configexc.ConfigErrorDesc(text, e)) self.errors.append(configexc.ConfigErrorDesc(text, e))
def finalize(self) -> None: def finalize(self):
"""Do work which needs to be done after reading config.py.""" """Do work which needs to be done after reading config.py."""
self._config.update_mutables() self._config.update_mutables()
def load_autoconfig(self) -> None: def load_autoconfig(self):
"""Load the autoconfig.yml file which is used for :set/:bind/etc.""" """Load the autoconfig.yml file which is used for :set/:bind/etc."""
with self._handle_error('reading', 'autoconfig.yml'): with self._handle_error('reading', 'autoconfig.yml'):
read_autoconfig() read_autoconfig()
def get(self, name: str, pattern: str = None) -> typing.Any: def get(self, name, pattern=None):
"""Get a setting value from the config, optionally with a pattern.""" """Get a setting value from the config, optionally with a pattern."""
with self._handle_error('getting', name): with self._handle_error('getting', name):
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
return self._config.get_mutable_obj(name, pattern=urlpattern) return self._config.get_mutable_obj(name, pattern=urlpattern)
def set(self, name: str, value: typing.Any, pattern: str = None) -> None: def set(self, name, value, pattern=None):
"""Set a setting value in the config, optionally with a pattern.""" """Set a setting value in the config, optionally with a pattern."""
with self._handle_error('setting', name): with self._handle_error('setting', name):
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
self._config.set_obj(name, value, pattern=urlpattern) self._config.set_obj(name, value, pattern=urlpattern)
def bind(self, key: str, command: str, mode: str = 'normal') -> None: def bind(self, key, command, mode='normal'):
"""Bind a key to a command, with an optional key mode.""" """Bind a key to a command, with an optional key mode."""
with self._handle_error('binding', key): with self._handle_error('binding', key):
seq = keyutils.KeySequence.parse(key) seq = keyutils.KeySequence.parse(key)
self._keyconfig.bind(seq, command, mode=mode) self._keyconfig.bind(seq, command, mode=mode)
def unbind(self, key: str, mode: str = 'normal') -> None: def unbind(self, key, mode='normal'):
"""Unbind a key from a command, with an optional key mode.""" """Unbind a key from a command, with an optional key mode."""
with self._handle_error('unbinding', key): with self._handle_error('unbinding', key):
seq = keyutils.KeySequence.parse(key) seq = keyutils.KeySequence.parse(key)
self._keyconfig.unbind(seq, mode=mode) self._keyconfig.unbind(seq, mode=mode)
def source(self, filename: str) -> None: def source(self, filename):
"""Read the given config file from disk.""" """Read the given config file from disk."""
if not os.path.isabs(filename): if not os.path.isabs(filename):
filename = str(self.configdir / filename) filename = str(self.configdir / filename)
@ -432,7 +416,7 @@ class ConfigAPI:
self.errors += e.errors self.errors += e.errors
@contextlib.contextmanager @contextlib.contextmanager
def pattern(self, pattern: str) -> typing.Iterator[config.ConfigContainer]: def pattern(self, pattern):
"""Get a ConfigContainer for the given pattern.""" """Get a ConfigContainer for the given pattern."""
# We need to propagate the exception so we don't need to return # We need to propagate the exception so we don't need to return
# something. # something.
@ -446,21 +430,17 @@ class ConfigPyWriter:
"""Writer for config.py files from given settings.""" """Writer for config.py files from given settings."""
def __init__( def __init__(self, options, bindings, *, commented):
self,
options: typing.List,
bindings: typing.MutableMapping[str, typing.Mapping[str, str]], *,
commented: bool) -> None:
self._options = options self._options = options
self._bindings = bindings self._bindings = bindings
self._commented = commented self._commented = commented
def write(self, filename: str) -> None: def write(self, filename):
"""Write the config to the given file.""" """Write the config to the given file."""
with open(filename, 'w', encoding='utf-8') as f: with open(filename, 'w', encoding='utf-8') as f:
f.write('\n'.join(self._gen_lines())) f.write('\n'.join(self._gen_lines()))
def _line(self, line: str) -> str: def _line(self, line):
"""Get an (optionally commented) line.""" """Get an (optionally commented) line."""
if self._commented: if self._commented:
if line.startswith('#'): if line.startswith('#'):
@ -470,7 +450,7 @@ class ConfigPyWriter:
else: else:
return line return line
def _gen_lines(self) -> typing.Iterator[str]: def _gen_lines(self):
"""Generate a config.py with the given settings/bindings. """Generate a config.py with the given settings/bindings.
Yields individual lines. Yields individual lines.
@ -479,7 +459,7 @@ class ConfigPyWriter:
yield from self._gen_options() yield from self._gen_options()
yield from self._gen_bindings() yield from self._gen_bindings()
def _gen_header(self) -> typing.Iterator[str]: def _gen_header(self):
"""Generate the initial header of the config.""" """Generate the initial header of the config."""
yield self._line("# Autogenerated config.py") yield self._line("# Autogenerated config.py")
yield self._line("# Documentation:") yield self._line("# Documentation:")
@ -501,7 +481,7 @@ class ConfigPyWriter:
yield self._line("# config.load_autoconfig()") yield self._line("# config.load_autoconfig()")
yield '' yield ''
def _gen_options(self) -> typing.Iterator[str]: def _gen_options(self):
"""Generate the options part of the config.""" """Generate the options part of the config."""
for pattern, opt, value in self._options: for pattern, opt, value in self._options:
if opt.name in ['bindings.commands', 'bindings.default']: if opt.name in ['bindings.commands', 'bindings.default']:
@ -529,7 +509,7 @@ class ConfigPyWriter:
opt.name, value, str(pattern))) opt.name, value, str(pattern)))
yield '' yield ''
def _gen_bindings(self) -> typing.Iterator[str]: def _gen_bindings(self):
"""Generate the bindings part of the config.""" """Generate the bindings part of the config."""
normal_bindings = self._bindings.pop('normal', {}) normal_bindings = self._bindings.pop('normal', {})
if normal_bindings: if normal_bindings:
@ -547,7 +527,7 @@ class ConfigPyWriter:
yield '' yield ''
def read_config_py(filename: str, raising: bool = False) -> None: def read_config_py(filename, raising=False):
"""Read a config.py file. """Read a config.py file.
Arguments; Arguments;
@ -563,8 +543,8 @@ def read_config_py(filename: str, raising: bool = False) -> None:
basename = os.path.basename(filename) basename = os.path.basename(filename)
module = types.ModuleType('config') module = types.ModuleType('config')
module.config = api # type: ignore module.config = api
module.c = container # type: ignore module.c = container
module.__file__ = filename module.__file__ = filename
try: try:
@ -609,7 +589,7 @@ def read_config_py(filename: str, raising: bool = False) -> None:
raise configexc.ConfigFileErrors('config.py', api.errors) raise configexc.ConfigFileErrors('config.py', api.errors)
def read_autoconfig() -> None: def read_autoconfig():
"""Read the autoconfig.yml file.""" """Read the autoconfig.yml file."""
try: try:
config.instance.read_yaml() config.instance.read_yaml()
@ -621,7 +601,7 @@ def read_autoconfig() -> None:
@contextlib.contextmanager @contextlib.contextmanager
def saved_sys_properties() -> typing.Iterator[None]: def saved_sys_properties():
"""Save various sys properties such as sys.path and sys.modules.""" """Save various sys properties such as sys.path and sys.modules."""
old_path = sys.path.copy() old_path = sys.path.copy()
old_modules = sys.modules.copy() old_modules = sys.modules.copy()
@ -634,7 +614,7 @@ def saved_sys_properties() -> typing.Iterator[None]:
del sys.modules[module] del sys.modules[module]
def init() -> None: def init():
"""Initialize config storage not related to the main config.""" """Initialize config storage not related to the main config."""
global state global state
state = StateConfig() state = StateConfig()

View File

@ -19,10 +19,8 @@
"""Initialization of the configuration.""" """Initialization of the configuration."""
import argparse
import os.path import os.path
import sys import sys
import typing
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
@ -32,14 +30,14 @@ from qutebrowser.config import (config, configdata, configfiles, configtypes,
from qutebrowser.utils import (objreg, usertypes, log, standarddir, message, from qutebrowser.utils import (objreg, usertypes, log, standarddir, message,
qtutils) qtutils)
from qutebrowser.config import configcache from qutebrowser.config import configcache
from qutebrowser.misc import msgbox, objects, savemanager from qutebrowser.misc import msgbox, objects
# Error which happened during init, so we can show a message box. # Error which happened during init, so we can show a message box.
_init_errors = None _init_errors = None
def early_init(args: argparse.Namespace) -> None: def early_init(args):
"""Initialize the part of the config which works without a QApplication.""" """Initialize the part of the config which works without a QApplication."""
configdata.init() configdata.init()
@ -87,7 +85,7 @@ def early_init(args: argparse.Namespace) -> None:
_init_envvars() _init_envvars()
def _init_envvars() -> None: def _init_envvars():
"""Initialize environment variables which need to be set early.""" """Initialize environment variables which need to be set early."""
if objects.backend == usertypes.Backend.QtWebEngine: if objects.backend == usertypes.Backend.QtWebEngine:
software_rendering = config.val.qt.force_software_rendering software_rendering = config.val.qt.force_software_rendering
@ -109,7 +107,7 @@ def _init_envvars() -> None:
@config.change_filter('fonts.monospace', function=True) @config.change_filter('fonts.monospace', function=True)
def _update_monospace_fonts() -> None: def _update_monospace_fonts():
"""Update all fonts if fonts.monospace was set.""" """Update all fonts if fonts.monospace was set."""
configtypes.Font.monospace_fonts = config.val.fonts.monospace configtypes.Font.monospace_fonts = config.val.fonts.monospace
for name, opt in configdata.DATA.items(): for name, opt in configdata.DATA.items():
@ -125,7 +123,7 @@ def _update_monospace_fonts() -> None:
config.instance.changed.emit(name) config.instance.changed.emit(name)
def get_backend(args: argparse.Namespace) -> usertypes.Backend: def get_backend(args):
"""Find out what backend to use based on available libraries.""" """Find out what backend to use based on available libraries."""
str_to_backend = { str_to_backend = {
'webkit': usertypes.Backend.QtWebKit, 'webkit': usertypes.Backend.QtWebKit,
@ -138,7 +136,7 @@ def get_backend(args: argparse.Namespace) -> usertypes.Backend:
return str_to_backend[config.val.backend] return str_to_backend[config.val.backend]
def late_init(save_manager: savemanager.SaveManager) -> None: def late_init(save_manager):
"""Initialize the rest of the config after the QApplication is created.""" """Initialize the rest of the config after the QApplication is created."""
global _init_errors global _init_errors
if _init_errors is not None: if _init_errors is not None:
@ -154,7 +152,7 @@ def late_init(save_manager: savemanager.SaveManager) -> None:
configfiles.state.init_save_manager(save_manager) configfiles.state.init_save_manager(save_manager)
def qt_args(namespace: argparse.Namespace) -> typing.List[str]: def qt_args(namespace):
"""Get the Qt QApplication arguments based on an argparse namespace. """Get the Qt QApplication arguments based on an argparse namespace.
Args: Args:
@ -180,7 +178,7 @@ def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
return argv return argv
def _qtwebengine_args() -> typing.Iterator[str]: def _qtwebengine_args():
"""Get the QtWebEngine arguments to use based on the config.""" """Get the QtWebEngine arguments to use based on the config."""
if not qtutils.version_check('5.11', compiled=False): if not qtutils.version_check('5.11', compiled=False):
# WORKAROUND equivalent to # WORKAROUND equivalent to
@ -226,7 +224,7 @@ def _qtwebengine_args() -> typing.Iterator[str]:
'never': '--no-referrers', 'never': '--no-referrers',
'same-domain': '--reduced-referrer-granularity', 'same-domain': '--reduced-referrer-granularity',
} }
} # type: typing.Dict[str, typing.Dict[typing.Any, typing.Optional[str]]] }
if not qtutils.version_check('5.11'): if not qtutils.version_check('5.11'):
# On Qt 5.11, we can control this via QWebEngineSettings # On Qt 5.11, we can control this via QWebEngineSettings

View File

@ -21,19 +21,11 @@
"""Utilities and data structures used by various config code.""" """Utilities and data structures used by various config code."""
import typing
import attr import attr
from PyQt5.QtCore import QUrl
from qutebrowser.utils import utils, urlmatch from qutebrowser.utils import utils
from qutebrowser.config import configexc from qutebrowser.config import configexc
MYPY = False
if MYPY: # pragma: no cover
# pylint: disable=unused-import,useless-suppression
from qutebrowser.config import configdata
class _UnsetObject: class _UnsetObject:
@ -41,7 +33,7 @@ class _UnsetObject:
__slots__ = () __slots__ = ()
def __repr__(self) -> str: def __repr__(self):
return '<UNSET>' return '<UNSET>'
@ -58,8 +50,8 @@ class ScopedValue:
pattern: The UrlPattern for the value, or None for global values. pattern: The UrlPattern for the value, or None for global values.
""" """
value = attr.ib() # type: typing.Any value = attr.ib()
pattern = attr.ib() # type: typing.Optional[urlmatch.UrlPattern] pattern = attr.ib()
class Values: class Values:
@ -81,17 +73,15 @@ class Values:
opt: The Option being customized. opt: The Option being customized.
""" """
def __init__(self, def __init__(self, opt, values=None):
opt: 'configdata.Option',
values: typing.MutableSequence = None) -> None:
self.opt = opt self.opt = opt
self._values = values or [] self._values = values or []
def __repr__(self) -> str: def __repr__(self):
return utils.get_repr(self, opt=self.opt, values=self._values, return utils.get_repr(self, opt=self.opt, values=self._values,
constructor=True) constructor=True)
def __str__(self) -> str: def __str__(self):
"""Get the values as human-readable string.""" """Get the values as human-readable string."""
if not self: if not self:
return '{}: <unchanged>'.format(self.opt.name) return '{}: <unchanged>'.format(self.opt.name)
@ -106,7 +96,7 @@ class Values:
scoped.pattern, self.opt.name, str_value)) scoped.pattern, self.opt.name, str_value))
return '\n'.join(lines) return '\n'.join(lines)
def __iter__(self) -> typing.Iterator['ScopedValue']: def __iter__(self):
"""Yield ScopedValue elements. """Yield ScopedValue elements.
This yields in "normal" order, i.e. global and then first-set settings This yields in "normal" order, i.e. global and then first-set settings
@ -114,25 +104,23 @@ class Values:
""" """
yield from self._values yield from self._values
def __bool__(self) -> bool: def __bool__(self):
"""Check whether this value is customized.""" """Check whether this value is customized."""
return bool(self._values) return bool(self._values)
def _check_pattern_support( def _check_pattern_support(self, arg):
self, arg: typing.Optional[urlmatch.UrlPattern]) -> None:
"""Make sure patterns are supported if one was given.""" """Make sure patterns are supported if one was given."""
if arg is not None and not self.opt.supports_pattern: if arg is not None and not self.opt.supports_pattern:
raise configexc.NoPatternError(self.opt.name) raise configexc.NoPatternError(self.opt.name)
def add(self, value: typing.Any, def add(self, value, pattern=None):
pattern: urlmatch.UrlPattern = None) -> None:
"""Add a value with the given pattern to the list of values.""" """Add a value with the given pattern to the list of values."""
self._check_pattern_support(pattern) self._check_pattern_support(pattern)
self.remove(pattern) self.remove(pattern)
scoped = ScopedValue(value, pattern) scoped = ScopedValue(value, pattern)
self._values.append(scoped) self._values.append(scoped)
def remove(self, pattern: urlmatch.UrlPattern = None) -> bool: def remove(self, pattern=None):
"""Remove the value with the given pattern. """Remove the value with the given pattern.
If a matching pattern was removed, True is returned. If a matching pattern was removed, True is returned.
@ -143,11 +131,11 @@ class Values:
self._values = [v for v in self._values if v.pattern != pattern] self._values = [v for v in self._values if v.pattern != pattern]
return old_len != len(self._values) return old_len != len(self._values)
def clear(self) -> None: def clear(self):
"""Clear all customization for this value.""" """Clear all customization for this value."""
self._values = [] self._values = []
def _get_fallback(self, fallback: typing.Any) -> typing.Any: def _get_fallback(self, fallback):
"""Get the fallback global/default value.""" """Get the fallback global/default value."""
for scoped in self._values: for scoped in self._values:
if scoped.pattern is None: if scoped.pattern is None:
@ -158,8 +146,7 @@ class Values:
else: else:
return UNSET return UNSET
def get_for_url(self, url: QUrl = None, *, def get_for_url(self, url=None, *, fallback=True):
fallback: bool = True) -> typing.Any:
"""Get a config value, falling back when needed. """Get a config value, falling back when needed.
This first tries to find a value matching the URL (if given). This first tries to find a value matching the URL (if given).
@ -178,9 +165,7 @@ class Values:
return self._get_fallback(fallback) return self._get_fallback(fallback)
def get_for_pattern(self, def get_for_pattern(self, pattern, *, fallback=True):
pattern: typing.Optional[urlmatch.UrlPattern], *,
fallback: bool = True) -> typing.Any:
"""Get a value only if it's been overridden for the given pattern. """Get a value only if it's been overridden for the given pattern.
This is useful when showing values to the user. This is useful when showing values to the user.