Remove most legacy config code
This commit is contained in:
parent
785de9fb99
commit
bc8176ff21
File diff suppressed because it is too large
Load Diff
@ -1,335 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""New qutebrowser configuration code."""
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.config import configdata, configexc, configtypes
|
||||
from qutebrowser.utils import utils, objreg, message
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
|
||||
# An easy way to access the config from other code via config.val.foo
|
||||
val = None
|
||||
instance = None
|
||||
|
||||
_change_filters = []
|
||||
|
||||
|
||||
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 NewKeyConfig:
|
||||
|
||||
def get_reverse_bindings_for(self, section):
|
||||
"""Get a dict of commands to a list of bindings for the section."""
|
||||
cmd_to_keys = {}
|
||||
bindings = val.bindings.commands[section]
|
||||
if bindings is None:
|
||||
return cmd_to_keys
|
||||
for key, full_cmd in bindings.items():
|
||||
for cmd in full_cmd.split(';;'):
|
||||
cmd = cmd.strip()
|
||||
cmd_to_keys.setdefault(cmd, [])
|
||||
# put special bindings last
|
||||
if utils.is_special_key(key):
|
||||
cmd_to_keys[cmd].append(key)
|
||||
else:
|
||||
cmd_to_keys[cmd].insert(0, key)
|
||||
return cmd_to_keys
|
||||
|
||||
|
||||
class ConfigCommands:
|
||||
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
|
||||
@cmdutils.register(instance='config-commands', star_args_optional=True)
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def set(self, win_id, option=None, *values, temp=False, print_=False):
|
||||
"""Set an option.
|
||||
|
||||
If the option name ends with '?', the value of the option is shown
|
||||
instead.
|
||||
|
||||
If the option name ends with '!' and it is a boolean value, toggle it.
|
||||
|
||||
//
|
||||
|
||||
Args:
|
||||
option: The name of the option.
|
||||
values: The value to set, or the values to cycle through.
|
||||
temp: Set value temporarily.
|
||||
print_: Print the value after setting.
|
||||
"""
|
||||
# FIXME:conf write to YAML if temp isn't used!
|
||||
if option is None:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
|
||||
return
|
||||
|
||||
if option.endswith('?') and option != '?':
|
||||
self._print_value(option[:-1])
|
||||
return
|
||||
|
||||
with self._handle_config_error():
|
||||
if option.endswith('!') and option != '!' and not values:
|
||||
# Handle inversion as special cases of the cycle code path
|
||||
option = option[:-1]
|
||||
opt = self._config.get_opt(option)
|
||||
if opt.typ is configtypes.Bool:
|
||||
values = ['false', 'true']
|
||||
else:
|
||||
raise cmdexc.CommandError(
|
||||
"set: Attempted inversion of non-boolean value.")
|
||||
elif not values:
|
||||
raise cmdexc.CommandError("set: The following arguments "
|
||||
"are required: value")
|
||||
self._set_next(option, values)
|
||||
|
||||
if print_:
|
||||
self._print_value(option)
|
||||
|
||||
def _print_value(self, option):
|
||||
"""Print the value of the given option."""
|
||||
with self._handle_config_error():
|
||||
val = self._config.get_str(option)
|
||||
message.info("{} = {}".format(option, val))
|
||||
|
||||
def _set_next(self, option, values):
|
||||
"""Set the next value out of a list of values."""
|
||||
if len(values) == 1:
|
||||
# If we have only one value, just set it directly (avoid
|
||||
# breaking stuff like aliases or other pseudo-settings)
|
||||
self._config.set(option, values[0])
|
||||
return
|
||||
|
||||
# Use the next valid value from values, or the first if the current
|
||||
# value does not appear in the list
|
||||
val = self._config.get_str(option)
|
||||
try:
|
||||
idx = values.index(str(val))
|
||||
idx = (idx + 1) % len(values)
|
||||
value = values[idx]
|
||||
except ValueError:
|
||||
value = values[0]
|
||||
self._config.set(option, value)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_config_error(self):
|
||||
"""Catch errors in set_command and raise CommandError."""
|
||||
try:
|
||||
yield
|
||||
except (configexc.NoOptionError, configexc.ValidationError) as e:
|
||||
raise cmdexc.CommandError("set: {}".format(e))
|
||||
except configexc.Error as e:
|
||||
raise cmdexc.CommandError("set: {} - {}".format(
|
||||
e.__class__.__name__, e))
|
||||
|
||||
|
||||
class NewConfigManager(QObject):
|
||||
|
||||
changed = pyqtSignal(str) # FIXME:conf stub...
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.options = {}
|
||||
self._values = {} # FIXME:conf stub
|
||||
|
||||
def read_defaults(self):
|
||||
for name, option in configdata.DATA.items():
|
||||
self.options[name] = option
|
||||
|
||||
def get_opt(self, name):
|
||||
try:
|
||||
return self.options[name]
|
||||
except KeyError:
|
||||
raise configexc.NoOptionError(name)
|
||||
|
||||
def get(self, name):
|
||||
opt = self.get_opt(name)
|
||||
value = self._values.get(name, opt.default)
|
||||
return opt.typ.to_py(value)
|
||||
|
||||
def get_str(self, name):
|
||||
opt = self.get_opt(name)
|
||||
return opt.typ.to_str(opt.default)
|
||||
|
||||
def set(self, name, value):
|
||||
# FIXME:conf stub
|
||||
try:
|
||||
opt = self.options[name]
|
||||
except KeyError:
|
||||
raise configexc.NoOptionError(name)
|
||||
self._values[name] = opt.typ.from_str(value)
|
||||
self.changed.emit(name)
|
||||
|
||||
def dump_userconfig(self):
|
||||
"""Get the part of the config which was changed by the user.
|
||||
|
||||
Return:
|
||||
The changed config part as string.
|
||||
"""
|
||||
lines = ['{} = {}'.format(optname, value)
|
||||
for optname, value in self._values.items()]
|
||||
if not lines:
|
||||
lines = ['<Default configuration>']
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class ConfigContainer:
|
||||
|
||||
"""An object implementing config access via __getattr__.
|
||||
|
||||
Attributes:
|
||||
_manager: The ConfigManager object.
|
||||
_prefix: The __getattr__ chain leading up to this object.
|
||||
"""
|
||||
|
||||
def __init__(self, manager, prefix=''):
|
||||
self._manager = manager
|
||||
self._prefix = prefix
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True, manager=self._manager,
|
||||
prefix=self._prefix)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""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 a part of an option name, we return a new ConfigContainer.
|
||||
|
||||
Those two never overlap as configdata.py ensures there are no shadowing
|
||||
options.
|
||||
"""
|
||||
name = self._join(attr)
|
||||
if configdata.is_valid_prefix(name):
|
||||
return ConfigContainer(manager=self._manager, prefix=name)
|
||||
try:
|
||||
return self._manager.get(name)
|
||||
except configexc.NoOptionError as e:
|
||||
# If it's not a valid prefix - re-raise to improve error text.
|
||||
raise configexc.NoOptionError(name)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if attr.startswith('_'):
|
||||
return super().__setattr__(attr, value)
|
||||
self._handler(self._join(attr), value)
|
||||
|
||||
def _join(self, attr):
|
||||
if self._prefix:
|
||||
return '{}.{}'.format(self._prefix, attr)
|
||||
else:
|
||||
return attr
|
||||
|
||||
|
||||
def init(parent):
|
||||
new_config = NewConfigManager(parent)
|
||||
new_config.read_defaults()
|
||||
objreg.register('config', new_config)
|
||||
|
||||
config_commands = ConfigCommands(new_config)
|
||||
objreg.register('config-commands', config_commands)
|
||||
|
||||
global val, instance, key_instance
|
||||
val = ConfigContainer(new_config)
|
||||
instance = new_config
|
||||
key_instance = NewKeyConfig()
|
||||
|
||||
for cf in _change_filters:
|
||||
cf.validate()
|
@ -1,74 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Parsers for INI-like config files, based on Python's ConfigParser."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import configparser
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils
|
||||
|
||||
|
||||
class ReadConfigParser(configparser.ConfigParser):
|
||||
|
||||
"""Our own ConfigParser subclass to read the main config.
|
||||
|
||||
Attributes:
|
||||
_configdir: The directory to read the config from.
|
||||
_fname: The filename of the config.
|
||||
_configfile: The config file path.
|
||||
"""
|
||||
|
||||
def __init__(self, configdir, fname):
|
||||
"""Config constructor.
|
||||
|
||||
Args:
|
||||
configdir: Directory to read the config from.
|
||||
fname: Filename of the config file.
|
||||
"""
|
||||
super().__init__(interpolation=None, comment_prefixes='#')
|
||||
self.optionxform = lambda opt: opt # be case-insensitive
|
||||
self._configdir = configdir
|
||||
self._fname = fname
|
||||
self._configfile = os.path.join(self._configdir, fname)
|
||||
|
||||
if not os.path.isfile(self._configfile):
|
||||
return
|
||||
log.init.debug("Reading config from {}".format(self._configfile))
|
||||
self.read(self._configfile, encoding='utf-8')
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True,
|
||||
configdir=self._configdir, fname=self._fname)
|
||||
|
||||
|
||||
class ReadWriteConfigParser(ReadConfigParser):
|
||||
|
||||
"""ConfigParser subclass used for auxiliary config files."""
|
||||
|
||||
def save(self):
|
||||
"""Save the config file."""
|
||||
if self._configdir is None:
|
||||
return
|
||||
if not os.path.exists(self._configdir):
|
||||
os.makedirs(self._configdir, 0o755)
|
||||
log.destroy.debug("Saving config to {}".format(self._configfile))
|
||||
with qtutils.savefile_open(self._configfile) as f:
|
||||
self.write(f)
|
@ -1,231 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Setting sections used for qutebrowser."""
|
||||
|
||||
import collections
|
||||
|
||||
from qutebrowser.config import value as confvalue
|
||||
|
||||
|
||||
class Section:
|
||||
|
||||
"""Base class for KeyValue/ValueList sections.
|
||||
|
||||
Attributes:
|
||||
_readonly: Whether this section is read-only.
|
||||
values: An OrderedDict with key as index and value as value.
|
||||
key: string
|
||||
value: SettingValue
|
||||
descriptions: A dict with the description strings for the keys.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.values = None
|
||||
self.descriptions = {}
|
||||
self._readonly = False
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get the value for key.
|
||||
|
||||
Args:
|
||||
key: The key to get a value for, as a string.
|
||||
|
||||
Return:
|
||||
The value, as value class.
|
||||
"""
|
||||
return self.values[key]
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over all set values."""
|
||||
return iter(self.values)
|
||||
|
||||
def __bool__(self):
|
||||
"""Get boolean state of section."""
|
||||
return bool(self.values)
|
||||
|
||||
def __contains__(self, key):
|
||||
"""Return whether the section contains a given key."""
|
||||
return key in self.values
|
||||
|
||||
def items(self):
|
||||
"""Get dict items."""
|
||||
return self.values.items()
|
||||
|
||||
def keys(self):
|
||||
"""Get value keys."""
|
||||
return self.values.keys()
|
||||
|
||||
def delete(self, key):
|
||||
"""Delete item with given key."""
|
||||
del self.values[key]
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
"""Set the value on a layer.
|
||||
|
||||
Args:
|
||||
layer: The layer to set the value on, an element name of the
|
||||
ValueLayers dict.
|
||||
key: The key of the element to set.
|
||||
value: The value to set.
|
||||
interpolated: The interpolated value, for checking, or None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def dump_userconfig(self):
|
||||
"""Dump the part of the config which was changed by the user.
|
||||
|
||||
Return:
|
||||
A list of (key, valuestr) tuples.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class KeyValue(Section):
|
||||
|
||||
"""Representation of a section with ordinary key-value mappings.
|
||||
|
||||
This is a section which contains normal "key = value" pairs with a fixed
|
||||
set of keys.
|
||||
"""
|
||||
|
||||
def __init__(self, *defaults, readonly=False):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
*defaults: A (key, value, description) list of defaults.
|
||||
readonly: Whether this config is readonly.
|
||||
"""
|
||||
super().__init__()
|
||||
self._readonly = readonly
|
||||
if not defaults:
|
||||
return
|
||||
self.values = collections.OrderedDict()
|
||||
for (k, v, desc) in defaults:
|
||||
assert k not in self.values, k
|
||||
self.values[k] = v
|
||||
self.descriptions[k] = desc
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
if self._readonly:
|
||||
raise ValueError("Trying to modify a read-only config!")
|
||||
self.values[key].setv(layer, value, interpolated)
|
||||
|
||||
def dump_userconfig(self):
|
||||
changed = []
|
||||
for k, v in self.items():
|
||||
vals = v.values
|
||||
if vals['temp'] is not None and vals['temp'] != vals['default']:
|
||||
changed.append((k, vals['temp']))
|
||||
elif vals['conf'] is not None and vals['conf'] != vals['default']:
|
||||
changed.append((k, vals['conf']))
|
||||
return changed
|
||||
|
||||
|
||||
class ValueList(Section):
|
||||
|
||||
"""This class represents a section with a list key-value settings.
|
||||
|
||||
These are settings inside sections which don't have fixed keys, but instead
|
||||
have a dynamic list of "key = value" pairs, like key bindings or
|
||||
searchengines.
|
||||
|
||||
They basically consist of two different SettingValues.
|
||||
|
||||
Attributes:
|
||||
layers: An OrderedDict of the config layers.
|
||||
keytype: The type to use for the key (only used for validating)
|
||||
valtype: The type to use for the value.
|
||||
_ordered_value_cache: A ChainMap-like OrderedDict of all values.
|
||||
_readonly: Whether this section is read-only.
|
||||
"""
|
||||
|
||||
def __init__(self, keytype, valtype, *defaults, readonly=False):
|
||||
"""Wrap types over default values. Take care when overriding this.
|
||||
|
||||
Args:
|
||||
keytype: The type instance to be used for keys.
|
||||
valtype: The type instance to be used for values.
|
||||
*defaults: A (key, value) list of default values.
|
||||
readonly: Whether this config is readonly.
|
||||
"""
|
||||
super().__init__()
|
||||
self._readonly = readonly
|
||||
self._ordered_value_cache = None
|
||||
self.keytype = keytype
|
||||
self.valtype = valtype
|
||||
self.layers = collections.OrderedDict([
|
||||
('default', collections.OrderedDict()),
|
||||
('conf', collections.OrderedDict()),
|
||||
('temp', collections.OrderedDict()),
|
||||
])
|
||||
defaultlayer = self.layers['default']
|
||||
for key, value in defaults:
|
||||
assert key not in defaultlayer, key
|
||||
defaultlayer[key] = confvalue.SettingValue(valtype, value)
|
||||
self.values = collections.ChainMap(
|
||||
self.layers['temp'], self.layers['conf'], self.layers['default'])
|
||||
|
||||
def _ordered_values(self):
|
||||
"""Get ordered values in layers.
|
||||
|
||||
This is more expensive than the ChainMap, but we need this for
|
||||
iterating/items/etc. when order matters.
|
||||
"""
|
||||
if self._ordered_value_cache is None:
|
||||
self._ordered_value_cache = collections.OrderedDict()
|
||||
for layer in self.layers.values():
|
||||
self._ordered_value_cache.update(layer)
|
||||
return self._ordered_value_cache
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
if self._readonly:
|
||||
raise ValueError("Trying to modify a read-only config!")
|
||||
self.keytype.validate(key)
|
||||
if key in self.layers[layer]:
|
||||
self.layers[layer][key].setv(layer, value, interpolated)
|
||||
else:
|
||||
val = confvalue.SettingValue(self.valtype)
|
||||
val.setv(layer, value, interpolated)
|
||||
self.layers[layer][key] = val
|
||||
self._ordered_value_cache = None
|
||||
|
||||
def dump_userconfig(self):
|
||||
changed = []
|
||||
mapping = collections.ChainMap(self.layers['temp'],
|
||||
self.layers['conf'])
|
||||
for k, v in mapping.items():
|
||||
try:
|
||||
if v.value() != self.layers['default'][k].value():
|
||||
changed.append((k, v.value()))
|
||||
except KeyError:
|
||||
changed.append((k, v.value()))
|
||||
return changed
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over all set values."""
|
||||
return self._ordered_values().__iter__()
|
||||
|
||||
def items(self):
|
||||
"""Get dict items."""
|
||||
return self._ordered_values().items()
|
||||
|
||||
def keys(self):
|
||||
"""Get value keys."""
|
||||
return self._ordered_values().keys()
|
@ -1,39 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Textwrapper used for config files."""
|
||||
|
||||
import textwrap
|
||||
|
||||
|
||||
class TextWrapper(textwrap.TextWrapper):
|
||||
|
||||
"""Text wrapper customized to be used in configs."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kw = {
|
||||
'width': 72,
|
||||
'replace_whitespace': False,
|
||||
'break_long_words': False,
|
||||
'break_on_hyphens': False,
|
||||
'initial_indent': '# ',
|
||||
'subsequent_indent': '# ',
|
||||
}
|
||||
kw.update(kwargs)
|
||||
super().__init__(**kw)
|
@ -1,101 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""A single value (with multiple layers possibly) in the config."""
|
||||
|
||||
import collections
|
||||
|
||||
|
||||
class SettingValue:
|
||||
|
||||
"""Base class for setting values.
|
||||
|
||||
Intended to be sub-classed by config value "types".
|
||||
|
||||
Attributes:
|
||||
typ: A BaseType subclass instance.
|
||||
value: (readonly property) The currently valid, most important value.
|
||||
values: An OrderedDict with the values on different layers, with the
|
||||
most significant layer first.
|
||||
"""
|
||||
|
||||
def __init__(self, typ, default=None, *, backends=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
typ: The BaseType to use.
|
||||
default: Raw value to set.
|
||||
backend: A list of usertypes.Backend enum members to mark this
|
||||
setting as unsupported with other backends.
|
||||
"""
|
||||
self.typ = typ
|
||||
self.values = collections.OrderedDict.fromkeys(
|
||||
['temp', 'conf', 'default'])
|
||||
self.values['default'] = default
|
||||
self.backends = backends
|
||||
|
||||
def __str__(self):
|
||||
"""Get raw string value."""
|
||||
return self.value()
|
||||
|
||||
def default(self):
|
||||
"""Get the default value."""
|
||||
return self.values['default']
|
||||
|
||||
def getlayers(self, startlayer):
|
||||
"""Get a dict of values starting with startlayer.
|
||||
|
||||
Args:
|
||||
startlayer: The first layer to include.
|
||||
"""
|
||||
idx = list(self.values.keys()).index(startlayer)
|
||||
d = collections.OrderedDict(list(self.values.items())[idx:])
|
||||
return d
|
||||
|
||||
def value(self, startlayer=None):
|
||||
"""Get the first valid value starting from startlayer.
|
||||
|
||||
Args:
|
||||
startlayer: The first layer to include.
|
||||
"""
|
||||
if startlayer is None:
|
||||
d = self.values
|
||||
else:
|
||||
d = self.getlayers(startlayer)
|
||||
for val in d.values():
|
||||
if val is not None:
|
||||
return val
|
||||
raise ValueError("No valid config value found!")
|
||||
|
||||
def transformed(self):
|
||||
"""Get the transformed value."""
|
||||
return self.typ.transform(self.value())
|
||||
|
||||
def setv(self, layer, value, interpolated):
|
||||
"""Set the value on a layer.
|
||||
|
||||
Args:
|
||||
layer: The layer to set the value on, an element name of the
|
||||
ValueLayers dict.
|
||||
value: The value to set.
|
||||
interpolated: The interpolated value, for typechecking (or None).
|
||||
"""
|
||||
if interpolated is not None:
|
||||
self.typ.validate(interpolated)
|
||||
self.values[layer] = value
|
@ -81,29 +81,6 @@ class TestConfigParser:
|
||||
with pytest.raises(configexc.ValidationError):
|
||||
objects.cfg._validate_all()
|
||||
|
||||
@pytest.mark.parametrize('config, sect1, opt1, sect2, opt2', [
|
||||
# Same section
|
||||
({'general': {'ignore-case': 'false',
|
||||
'private-browsing': '${ignore-case}'}},
|
||||
'general', 'ignore-case', 'general', 'private-browsing'),
|
||||
# Across sections
|
||||
({'general': {'ignore-case': '${network:do-not-track}'},
|
||||
'network': {'do-not-track': 'false'}},
|
||||
'general', 'ignore-case', 'network', 'do-not-track'),
|
||||
])
|
||||
def test_interpolation(self, objects, config, sect1, opt1, sect2, opt2):
|
||||
objects.cp.read_dict(config)
|
||||
objects.cfg._from_cp(objects.cp)
|
||||
assert not objects.cfg.get(sect1, opt1)
|
||||
assert not objects.cfg.get(sect2, opt2)
|
||||
|
||||
def test_invalid_interpolation(self, objects):
|
||||
"""Test an invalid interpolation."""
|
||||
objects.cp.read_dict({'general': {'ignore-case': '${foo}'}})
|
||||
objects.cfg._from_cp(objects.cp)
|
||||
with pytest.raises(configparser.InterpolationError):
|
||||
objects.cfg._validate_all()
|
||||
|
||||
@pytest.mark.parametrize('config, exception', [
|
||||
# Invalid interpolation syntax
|
||||
({'general': {'ignore-case': '${'}},
|
||||
@ -131,26 +108,6 @@ class TestConfigParser:
|
||||
with pytest.raises(exception):
|
||||
objects.cfg.get(sect, opt)
|
||||
|
||||
def test_fallback(self, objects):
|
||||
"""Test getting an option with fallback.
|
||||
|
||||
This is done during interpolation in later Python 3.4 versions.
|
||||
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/968
|
||||
"""
|
||||
assert objects.cfg.get('general', 'blabla', fallback='blub') == 'blub'
|
||||
|
||||
def test_sectionproxy(self, objects):
|
||||
"""Test getting an option via the section proxy."""
|
||||
objects.cp.read_dict({'general': {'ignore-case': 'false'}})
|
||||
objects.cfg._from_cp(objects.cp)
|
||||
assert not objects.cfg['general'].get('ignore-case')
|
||||
|
||||
def test_sectionproxy_keyerror(self, objects):
|
||||
"""Test getting an inexistent option via the section proxy."""
|
||||
with pytest.raises(configexc.NoOptionError):
|
||||
objects.cfg['general'].get('blahblahblub')
|
||||
|
||||
@pytest.mark.parametrize('old_sect, new_sect',
|
||||
config.ConfigManager.RENAMED_SECTIONS.items())
|
||||
def test_renamed_sections(self, old_sect, new_sect):
|
||||
@ -184,62 +141,6 @@ class TestConfigParser:
|
||||
assert objects.cfg.get('general', 'save-session')
|
||||
|
||||
|
||||
class TestTransformers:
|
||||
|
||||
"""Test value transformers in CHANGED_OPTIONS."""
|
||||
|
||||
@pytest.mark.parametrize('val, expected', [('a', 'b'), ('c', 'c')])
|
||||
def test_get_value_transformer(self, val, expected):
|
||||
func = config._get_value_transformer({'a': 'b'})
|
||||
assert func(val) == expected
|
||||
|
||||
@pytest.mark.parametrize('val, expected', [
|
||||
('top', 'top'),
|
||||
('north', 'top'),
|
||||
('south', 'bottom'),
|
||||
('west', 'left'),
|
||||
('east', 'right'),
|
||||
])
|
||||
def test_position(self, val, expected):
|
||||
func = config._transform_position
|
||||
assert func(val) == expected
|
||||
|
||||
OLD_GRADIENT = ('-webkit-gradient(linear, left top, left bottom, '
|
||||
'color-stop(0%,{}), color-stop(100%,{}))')
|
||||
NEW_GRADIENT = ('qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {}, '
|
||||
'stop:1 {})')
|
||||
|
||||
@pytest.mark.parametrize('val, expected', [
|
||||
('-unknown-stuff', None),
|
||||
('blue', 'blue'),
|
||||
('rgba(1, 2, 3, 4)', 'rgba(1, 2, 3, 4)'),
|
||||
('-webkit-gradient(unknown)', None),
|
||||
(OLD_GRADIENT.format('blah', 'blah'), None),
|
||||
(OLD_GRADIENT.format('red', 'green'),
|
||||
NEW_GRADIENT.format('rgba(255, 0, 0, 0.8)', 'rgba(0, 128, 0, 0.8)')),
|
||||
(OLD_GRADIENT.format(' red', ' green'),
|
||||
NEW_GRADIENT.format('rgba(255, 0, 0, 0.8)', 'rgba(0, 128, 0, 0.8)')),
|
||||
(OLD_GRADIENT.format('#101010', ' #202020'),
|
||||
NEW_GRADIENT.format('rgba(16, 16, 16, 0.8)',
|
||||
'rgba(32, 32, 32, 0.8)')),
|
||||
(OLD_GRADIENT.format('#666', ' #777'),
|
||||
NEW_GRADIENT.format('rgba(102, 102, 102, 0.8)',
|
||||
'rgba(119, 119, 119, 0.8)')),
|
||||
(OLD_GRADIENT.format('red', 'green') + 'more stuff', None),
|
||||
])
|
||||
def test_hint_color(self, val, expected):
|
||||
assert config._transform_hint_color(val) == expected
|
||||
|
||||
@pytest.mark.parametrize('val, expected', [
|
||||
('bold 12pt Monospace', 'bold 12pt ${_monospace}'),
|
||||
('23pt Monospace', '23pt ${_monospace}'),
|
||||
('bold 12pt ${_monospace}', 'bold 12pt ${_monospace}'),
|
||||
('bold 12pt Comic Sans MS', 'bold 12pt Comic Sans MS'),
|
||||
])
|
||||
def test_hint_font(self, val, expected):
|
||||
assert config._transform_hint_font(val) == expected
|
||||
|
||||
|
||||
class TestKeyConfigParser:
|
||||
|
||||
"""Test config.parsers.keyconf.KeyConfigParser."""
|
||||
|
@ -1,38 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Tests for config.textwrapper."""
|
||||
|
||||
from qutebrowser.config import textwrapper
|
||||
|
||||
|
||||
def test_default_args():
|
||||
wrapper = textwrapper.TextWrapper()
|
||||
assert wrapper.width == 72
|
||||
assert not wrapper.replace_whitespace
|
||||
assert not wrapper.break_long_words
|
||||
assert not wrapper.break_on_hyphens
|
||||
assert wrapper.initial_indent == '# '
|
||||
assert wrapper.subsequent_indent == '# '
|
||||
|
||||
|
||||
def test_custom_args():
|
||||
wrapper = textwrapper.TextWrapper(drop_whitespace=False)
|
||||
assert wrapper.width == 72
|
||||
assert not wrapper.drop_whitespace
|
Loading…
Reference in New Issue
Block a user