qutebrowser/qutebrowser/config/config.py

620 lines
21 KiB
Python
Raw Normal View History

2014-06-19 09:04:37 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2017-05-09 21:37:03 +02:00
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2014-02-06 14:01:23 +01:00
#
# 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/>.
2017-06-16 15:05:36 +02:00
"""Configuration storage and config-related utilities."""
2014-03-27 22:37:34 +01:00
import copy
2014-02-17 12:23:52 +01:00
import os.path
2017-06-16 15:05:36 +02:00
import contextlib
2014-08-27 20:16:04 +02:00
import functools
2014-01-27 21:42:00 +01:00
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
2017-06-20 16:27:54 +02:00
from qutebrowser.config import configdata, configexc, configtypes, configfiles
2017-06-19 16:41:17 +02:00
from qutebrowser.utils import (utils, objreg, message, standarddir, log,
usertypes, jinja)
2017-06-19 16:41:17 +02:00
from qutebrowser.commands import cmdexc, cmdutils, runners
2014-02-19 10:58:32 +01:00
2017-06-16 15:05:36 +02:00
# An easy way to access the config from other code via config.val.foo
val = None
2017-06-13 13:47:06 +02:00
instance = None
2017-06-16 13:28:51 +02:00
key_instance = None
2017-06-16 15:05:36 +02:00
# Keeping track of all change filters to validate them later.
_change_filters = []
2017-06-14 21:42:36 +02:00
2017-06-16 15:05:36 +02:00
class change_filter: # pylint: disable=invalid-name
2017-06-16 15:05:36 +02:00
"""Decorator to filter calls based on a config section/option matching.
2017-06-16 15:05:36 +02:00
This could also be a function, but as a class (with a "wrong" name) it's
much cleaner to implement.
2015-04-06 00:10:37 +02:00
2017-06-16 15:05:36 +02:00
Attributes:
_option: An option or prefix to be filtered
_function: Whether a function rather than a method is decorated.
2015-04-06 00:10:37 +02:00
"""
2017-06-16 15:05:36 +02:00
def __init__(self, option, function=False):
"""Save decorator arguments.
2017-06-16 15:05:36 +02:00
Gets called on parse-time with the decorator arguments.
2017-06-16 15:05:36 +02:00
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)
2014-09-28 00:27:22 +02:00
2017-06-16 15:05:36 +02:00
def validate(self):
"""Make sure the configured option or prefix exists.
2017-06-16 15:05:36 +02:00
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
2017-06-16 15:05:36 +02:00
def __call__(self, func):
"""Filter calls to the decorated function.
2014-09-28 00:27:22 +02:00
2017-06-16 15:05:36 +02:00
Gets called when a function should be decorated.
2017-06-16 15:05:36 +02:00
Adds a filter which returns if we're not interested in the change-event
and calls the wrapped function if we are.
2017-06-16 15:05:36 +02:00
We assume the function passed doesn't take any parameters.
2017-06-16 15:05:36 +02:00
Args:
func: The function to be decorated.
2017-06-16 15:05:36 +02:00
Return:
The decorated function.
"""
if self._function:
@functools.wraps(func)
def wrapper(option=None):
if self._check_match(option):
return func()
else:
2017-06-16 15:05:36 +02:00
@functools.wraps(func)
def wrapper(wrapper_self, option=None):
if self._check_match(option):
return func(wrapper_self)
return wrapper
2017-06-20 17:13:46 +02:00
class KeyConfig:
2017-06-16 15:05:36 +02:00
2017-06-20 17:13:46 +02:00
"""Utilities related to keybindings.
Note that the actual values are saved in the config itself, not here.
Attributes:
_config: The Config object to be used.
"""
def __init__(self, config):
self._config = config
2017-06-19 16:41:17 +02:00
2017-07-02 22:10:28 +02:00
def _prepare(self, key, mode):
"""Make sure the given mode exists and normalize the key."""
if mode not in configdata.DATA['bindings.default'].default:
raise configexc.KeybindingError("Invalid mode {}!".format(mode))
if utils.is_special_key(key):
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
return utils.normalize_keystr(key)
return key
def get_bindings_for(self, mode):
"""Get the combined bindings for the given mode."""
bindings = dict(val.bindings.default[mode])
for key, binding in val.bindings.commands[mode].items():
if binding is None:
2017-07-02 17:12:31 +02:00
bindings.pop(key, None)
else:
bindings[key] = binding
return bindings
2017-06-19 16:41:17 +02:00
def get_reverse_bindings_for(self, mode):
"""Get a dict of commands to a list of bindings for the mode."""
2017-06-16 15:05:36 +02:00
cmd_to_keys = {}
bindings = self.get_bindings_for(mode)
2017-07-02 17:12:31 +02:00
for key, full_cmd in sorted(bindings.items()):
2017-06-16 15:05:36 +02:00
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
2017-07-02 22:10:28 +02:00
def get_command(self, key, mode):
"""Get the command for a given key (or None)."""
key = self._prepare(key, mode)
bindings = self.get_bindings_for(mode)
return bindings.get(key, None)
2017-06-19 16:41:17 +02:00
2017-06-20 16:53:46 +02:00
def bind(self, key, command, *, mode, force=False, save_yaml=False):
2017-06-19 16:41:17 +02:00
"""Add a new binding from key to command."""
key = self._prepare(key, mode)
parser = runners.CommandParser()
try:
results = parser.parse_all(command)
except cmdexc.Error as e:
raise configexc.KeybindingError("Invalid command: {}".format(e))
2017-06-19 16:41:17 +02:00
2017-07-02 22:10:28 +02:00
for result in results: # pragma: no branch
2017-06-19 16:41:17 +02:00
try:
result.cmd.validate_mode(usertypes.KeyMode[mode])
except cmdexc.PrerequisitesError as e:
raise configexc.KeybindingError(str(e))
2017-06-19 16:41:17 +02:00
log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
key, command, mode))
if key in self.get_bindings_for(mode) and not force:
raise configexc.DuplicateKeyError(key)
bindings = self._config.get_obj('bindings.commands')
if mode not in bindings:
bindings[mode] = {}
bindings[mode][key] = command
2017-06-20 17:13:46 +02:00
self._config.update_mutables(save_yaml=save_yaml)
2017-06-19 16:41:17 +02:00
2017-06-20 16:53:46 +02:00
def unbind(self, key, *, mode='normal', save_yaml=False):
2017-06-19 16:41:17 +02:00
"""Unbind the given key in the given mode."""
key = self._prepare(key, mode)
bindings_commands = self._config.get_obj('bindings.commands')
if key in val.bindings.commands[mode]:
# In custom bindings -> remove it
del bindings_commands[mode][key]
elif key in val.bindings.default[mode]:
# In default bindings -> shadow it with None
if mode not in bindings_commands:
bindings_commands[mode] = {}
bindings_commands[mode][key] = None
else:
2017-07-02 22:10:28 +02:00
raise configexc.KeybindingError(
"Can't find binding '{}' in {} mode".format(key, mode))
2017-06-20 17:13:46 +02:00
self._config.update_mutables(save_yaml=save_yaml)
2017-06-19 16:41:17 +02:00
2017-06-16 15:05:36 +02:00
class ConfigCommands:
2017-06-20 17:13:46 +02:00
"""qutebrowser commands related to the configuration."""
def __init__(self, config, keyconfig):
2017-06-16 15:05:36 +02:00
self._config = config
self._keyconfig = keyconfig
2014-02-26 07:44:39 +01:00
2017-06-16 15:05:36 +02:00
@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.
2014-12-15 22:25:06 +01:00
2017-06-16 15:05:36 +02:00
If the option name ends with '?', the value of the option is shown
instead.
2014-12-15 22:25:06 +01:00
2017-06-16 15:05:36 +02:00
If the option name ends with '!' and it is a boolean value, toggle it.
2014-12-15 22:25:06 +01:00
2017-06-16 15:05:36 +02:00
//
Args:
2017-06-16 15:05:36 +02:00
option: The name of the option.
values: The value to set, or the values to cycle through.
2017-06-17 11:04:31 +02:00
temp: Set value temporarily until qutebrowser is closed.
2017-06-16 15:05:36 +02:00
print_: Print the value after setting.
"""
2017-06-16 15:05:36 +02:00
if option is None:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
return
2015-03-23 08:19:31 +01:00
2017-06-16 15:05:36 +02:00
if option.endswith('?') and option != '?':
self._print_value(option[:-1])
2015-03-23 08:19:31 +01:00
return
2017-06-16 15:05:36 +02:00
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)
2017-06-19 13:04:43 +02:00
if isinstance(opt.typ, configtypes.Bool):
2017-06-16 15:05:36 +02:00
values = ['false', 'true']
2015-03-23 08:19:31 +01:00
else:
2017-06-16 15:05:36 +02:00
raise cmdexc.CommandError(
2017-07-02 23:56:55 +02:00
"set: Can't toggle non-bool setting {}".format(option))
2017-06-16 15:05:36 +02:00
elif not values:
raise cmdexc.CommandError("set: The following arguments "
"are required: value")
2017-06-20 16:53:46 +02:00
self._set_next(option, values, temp=temp)
2017-06-16 15:05:36 +02:00
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))
2017-06-20 16:53:46 +02:00
def _set_next(self, option, values, *, temp):
2017-06-16 15:05:36 +02:00
"""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)
2017-06-20 16:53:46 +02:00
self._config.set_str(option, values[0], save_yaml=not temp)
2017-06-16 15:05:36 +02:00
return
2017-06-16 15:05:36 +02:00
# 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]
2017-06-20 16:53:46 +02:00
self._config.set_str(option, value, save_yaml=not temp)
2017-06-16 15:05:36 +02:00
@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))
2017-06-19 16:41:17 +02:00
@cmdutils.register(instance='config-commands', maxsplit=1,
no_cmd_split=True, no_replace_variables=True)
@cmdutils.argument('command', completion=usertypes.Completion.bind)
def bind(self, key, command=None, *, mode='normal', force=False):
"""Bind a key to a command.
Args:
key: The keychain or special key (inside `<...>`) to bind.
command: The command to execute, with optional args, or None to
print the current binding.
mode: A comma-separated list of modes to bind the key in
(default: `normal`).
force: Rebind the key if it is already bound.
"""
if command is None:
if utils.is_special_key(key):
# self._keyconfig.get_command does this, but we also need it
# normalized for the output below
key = utils.normalize_keystr(key)
cmd = self._keyconfig.get_command(key, mode)
2017-06-19 16:41:17 +02:00
if cmd is None:
message.info("{} is unbound in {} mode".format(key, mode))
else:
message.info("{} is bound to '{}' in {} mode".format(
key, cmd, mode))
return
try:
self._keyconfig.bind(key, command, mode=mode, force=force,
save_yaml=True)
2017-06-19 16:41:17 +02:00
except configexc.DuplicateKeyError as e:
raise cmdexc.CommandError("bind: {} - use --force to override!"
.format(e))
except configexc.KeybindingError as e:
raise cmdexc.CommandError("bind: {}".format(e))
2017-06-19 16:41:17 +02:00
@cmdutils.register(instance='config-commands')
def unbind(self, key, mode='normal'):
"""Unbind a keychain.
Args:
key: The keychain or special key (inside <...>) to unbind.
mode: A mode to unbind the key in (default: `normal`).
"""
try:
self._keyconfig.unbind(key, mode=mode, save_yaml=True)
except configexc.KeybindingError as e:
raise cmdexc.CommandError('unbind: {}'.format(e))
2017-06-19 16:41:17 +02:00
2017-06-20 17:13:46 +02:00
class Config(QObject):
"""Main config object.
Attributes:
_values: A dict mapping setting names to their values.
_mutables: A list of mutable objects to be checked for changes.
2017-07-02 21:07:38 +02:00
_yaml: A YamlConfig object or None.
2017-06-20 17:13:46 +02:00
Signals:
changed: Emitted with the option name when an option changed.
"""
2014-04-10 12:37:49 +02:00
2017-06-20 16:23:23 +02:00
changed = pyqtSignal(str)
2014-04-07 17:53:57 +02:00
2017-07-02 21:07:38 +02:00
def __init__(self, yaml_config, parent=None):
2017-06-16 15:05:36 +02:00
super().__init__(parent)
2017-06-20 16:23:23 +02:00
self._values = {}
self._mutables = []
2017-07-02 21:07:38 +02:00
self._yaml = yaml_config
2014-04-10 12:37:49 +02:00
2017-06-19 16:41:17 +02:00
def _changed(self, name, value):
2017-06-20 17:13:46 +02:00
"""Emit changed signal and log change."""
2017-06-19 16:41:17 +02:00
self.changed.emit(name)
log.config.debug("Config option changed: {} = {}".format(name, value))
2017-06-20 16:53:46 +02:00
def read_yaml(self):
2017-06-20 17:13:46 +02:00
"""Read the YAML settings from self._yaml."""
2017-06-20 16:53:46 +02:00
self._yaml.load()
for name, value in self._yaml.values.items():
opt = self.get_opt(name)
opt.typ.to_py(value) # for validation
self._values[name] = value
2017-06-16 15:05:36 +02:00
def get_opt(self, name):
2017-06-20 17:13:46 +02:00
"""Get a configdata.Option object for the given setting."""
2014-04-10 12:37:49 +02:00
try:
return configdata.DATA[name]
2014-04-10 12:37:49 +02:00
except KeyError:
2017-06-16 15:05:36 +02:00
raise configexc.NoOptionError(name)
2017-06-16 15:05:36 +02:00
def get(self, name):
2017-06-20 17:13:46 +02:00
"""Get the given setting converted for Python code."""
2017-06-16 15:05:36 +02:00
opt = self.get_opt(name)
obj = self.get_obj(name, mutable=False)
return opt.typ.to_py(obj)
def get_obj(self, name, *, mutable=True):
2017-06-20 17:13:46 +02:00
"""Get the given setting as object (for YAML/config.py).
If mutable=True is set, watch the returned object for mutations.
"""
opt = self.get_opt(name)
obj = self._values.get(name, opt.default)
if isinstance(obj, (dict, list)):
if mutable:
self._mutables.append((name, copy.deepcopy(obj), obj))
else:
# Shouldn't be mutable (and thus hashable)
assert obj.__hash__ is not None, obj
return obj
2016-08-03 11:35:08 +02:00
2017-06-16 15:05:36 +02:00
def get_str(self, name):
2017-06-20 17:13:46 +02:00
"""Get the given setting as string."""
2017-06-16 15:05:36 +02:00
opt = self.get_opt(name)
2017-06-19 13:00:10 +02:00
value = self._values.get(name, opt.default)
return opt.typ.to_str(value)
2016-08-03 11:35:08 +02:00
2017-06-20 16:53:46 +02:00
def set_obj(self, name, value, *, save_yaml=False):
2017-06-20 17:13:46 +02:00
"""Set the given setting from a YAML/config.py object.
If save_yaml=True is given, store the new value to YAML.
"""
2017-06-19 16:41:17 +02:00
opt = self.get_opt(name)
opt.typ.to_py(value) # for validation
self._values[name] = value
self._changed(name, value)
2017-06-20 16:53:46 +02:00
if save_yaml:
self._yaml.values[name] = value
2017-06-19 16:41:17 +02:00
2017-06-20 16:53:46 +02:00
def set_str(self, name, value, *, save_yaml=False):
2017-06-20 17:13:46 +02:00
"""Set the given setting from a string.
If save_yaml=True is given, store the new value to YAML.
"""
2017-06-19 13:00:34 +02:00
opt = self.get_opt(name)
2017-06-20 16:53:46 +02:00
converted = opt.typ.from_str(value)
self._values[name] = converted
self._changed(name, converted)
if save_yaml:
self._yaml.values[name] = converted
2014-04-09 22:44:07 +02:00
2017-06-20 16:53:46 +02:00
def update_mutables(self, *, save_yaml=False):
2017-06-20 17:13:46 +02:00
"""Update mutable settings if they changed.
Every time someone calls get_obj() on a mutable object, we save a
reference to the original object and a copy.
Here, we check all those saved copies for mutations, and if something
mutated, we call set_obj again so we save the new value.
"""
for name, old_value, new_value in self._mutables:
if old_value != new_value:
log.config.debug("{} was mutated, updating".format(name))
2017-06-20 16:53:46 +02:00
self.set_obj(name, new_value, save_yaml=save_yaml)
self._mutables = []
2017-06-16 15:05:36 +02:00
def dump_userconfig(self):
"""Get the part of the config which was changed by the user.
2014-02-26 07:44:39 +01:00
2017-06-16 15:05:36 +02:00
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)
2014-04-07 16:51:14 +02:00
2014-02-26 07:44:39 +01:00
2017-06-16 15:05:36 +02:00
class ConfigContainer:
2014-04-07 17:20:14 +02:00
2017-06-16 15:05:36 +02:00
"""An object implementing config access via __getattr__.
2014-04-10 12:37:49 +02:00
Attributes:
2017-06-20 17:13:46 +02:00
_config: The Config object.
2017-06-16 15:05:36 +02:00
_prefix: The __getattr__ chain leading up to this object.
2014-04-10 12:37:49 +02:00
"""
2014-03-27 22:37:34 +01:00
2017-06-20 17:13:46 +02:00
def __init__(self, config, prefix=''):
self._config = config
2017-06-16 15:05:36 +02:00
self._prefix = prefix
2014-04-10 12:37:49 +02:00
2017-06-16 15:05:36 +02:00
def __repr__(self):
2017-06-20 17:13:46 +02:00
return utils.get_repr(self, constructor=True, config=self._config,
2017-06-16 15:05:36 +02:00
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.
2014-04-10 12:37:49 +02:00
"""
2017-06-19 16:41:17 +02:00
if attr.startswith('_'):
return self.__getattribute__(attr)
2017-06-16 15:05:36 +02:00
name = self._join(attr)
if configdata.is_valid_prefix(name):
2017-06-20 17:13:46 +02:00
return ConfigContainer(config=self._config, prefix=name)
2017-06-19 16:41:17 +02:00
2017-06-16 15:05:36 +02:00
try:
2017-06-20 17:13:46 +02:00
return self._config.get(name)
2017-06-16 15:05:36 +02:00
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):
2017-06-20 17:13:46 +02:00
"""Set the given option in the config."""
2017-06-16 15:05:36 +02:00
if attr.startswith('_'):
return super().__setattr__(attr, value)
2017-06-20 17:13:46 +02:00
self._config.set_obj(self._join(attr), value)
2017-06-16 15:05:36 +02:00
def _join(self, attr):
2017-06-20 17:13:46 +02:00
"""Get the prefix joined with the given attribute."""
2017-06-16 15:05:36 +02:00
if self._prefix:
return '{}.{}'.format(self._prefix, attr)
else:
return attr
2014-04-10 12:37:49 +02:00
def set_register_stylesheet(obj, *, stylesheet=None, update=True):
"""Set the stylesheet for an object based on it's STYLESHEET attribute.
Also, register an update when the config is changed.
Args:
obj: The object to set the stylesheet for and register.
Must have a STYLESHEET attribute if stylesheet is not given.
stylesheet: The stylesheet to use.
update: Whether to update the stylesheet on config changes.
"""
observer = StyleSheetObserver(obj, stylesheet=stylesheet)
observer.register(update=update)
class StyleSheetObserver(QObject):
"""Set the stylesheet on the given object and update it on changes.
Attributes:
_obj: The object to observe.
_stylesheet: The stylesheet template to use.
"""
def __init__(self, obj, stylesheet):
super().__init__(parent=obj)
self._obj = obj
if stylesheet is None:
self._stylesheet = obj.STYLESHEET
else:
self._stylesheet = stylesheet
def _get_stylesheet(self):
"""Format a stylesheet based on a template.
Return:
The formatted template as string.
"""
template = jinja.environment.from_string(self._stylesheet)
return template.render(conf=val)
@pyqtSlot()
def _update_stylesheet(self):
"""Update the stylesheet for obj."""
self._obj.setStyleSheet(self._get_stylesheet())
def register(self, update):
"""Do a first update and listen for more.
Args:
update: if False, don't listen for future updates.
"""
qss = self._get_stylesheet()
log.config.vdebug("stylesheet for {}: {}".format(
self._obj.__class__.__name__, qss))
self._obj.setStyleSheet(qss)
if update:
instance.changed.connect(self._update_stylesheet)
2017-06-16 15:05:36 +02:00
def init(parent=None):
"""Initialize the config.
2014-03-27 22:37:34 +01:00
2017-06-16 15:05:36 +02:00
Args:
parent: The parent to pass to QObjects which get initialized.
"""
configdata.init()
2014-03-27 22:37:34 +01:00
2017-07-02 21:07:38 +02:00
yaml_config = configfiles.YamlConfig()
config = Config(yaml_config=yaml_config, parent=parent)
2017-06-20 17:13:46 +02:00
objreg.register('config', config)
2014-03-27 22:37:34 +01:00
2017-06-16 15:05:36 +02:00
global val, instance, key_instance
2017-06-20 17:13:46 +02:00
val = ConfigContainer(config)
instance = config
key_instance = KeyConfig(config)
2017-06-16 15:05:36 +02:00
config_commands = ConfigCommands(config, key_instance)
objreg.register('config-commands', config_commands)
2017-06-16 15:05:36 +02:00
for cf in _change_filters:
cf.validate()
config.read_yaml()
2017-06-16 15:05:36 +02:00
2017-06-20 16:27:54 +02:00
configfiles.init(instance)