2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2015-01-03 15:51:31 +01:00
|
|
|
# Copyright 2014-2015 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/>.
|
|
|
|
|
2014-03-27 22:37:34 +01:00
|
|
|
"""Configuration storage and config-related utilities.
|
|
|
|
|
|
|
|
This borrows a lot of ideas from configparser, but also has some things that
|
|
|
|
are fundamentally different. This is why nothing inherts from configparser, but
|
|
|
|
we borrow some methods and classes from there where it makes sense.
|
|
|
|
"""
|
2014-02-17 12:23:52 +01:00
|
|
|
|
2014-01-27 21:42:00 +01:00
|
|
|
import os
|
2014-09-28 00:27:22 +02:00
|
|
|
import sys
|
2014-02-17 12:23:52 +01:00
|
|
|
import os.path
|
2014-08-27 20:16:04 +02:00
|
|
|
import functools
|
2014-03-27 22:37:34 +01:00
|
|
|
import configparser
|
2014-09-28 11:27:52 +02:00
|
|
|
import collections
|
2014-08-26 19:10:14 +02:00
|
|
|
import collections.abc
|
2014-01-27 21:42:00 +01:00
|
|
|
|
2014-12-01 23:09:01 +01:00
|
|
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QStandardPaths, QUrl
|
2014-09-28 00:27:22 +02:00
|
|
|
from PyQt5.QtWidgets import QMessageBox
|
2014-04-10 18:01:16 +02:00
|
|
|
|
2014-12-17 11:17:18 +01:00
|
|
|
from qutebrowser.config import configdata, configexc, textwrapper
|
2014-12-13 17:28:50 +01:00
|
|
|
from qutebrowser.config.parsers import ini, keyconf
|
2014-08-26 20:48:39 +02:00
|
|
|
from qutebrowser.commands import cmdexc, cmdutils
|
2014-12-08 23:42:26 +01:00
|
|
|
from qutebrowser.utils import message, objreg, utils, standarddir, log, qtutils
|
2014-07-29 00:37:32 +02:00
|
|
|
from qutebrowser.utils.usertypes import Completion
|
2014-01-28 23:04:02 +01:00
|
|
|
|
2014-02-19 10:58:32 +01:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
class change_filter: # pylint: disable=invalid-name
|
2014-09-28 11:27:52 +02:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
"""Decorator to register a new command handler.
|
2014-09-28 11:27:52 +02:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
This could also be a function, but as a class (with a "wrong" name) it's
|
|
|
|
much cleaner to implement.
|
2014-09-28 11:27:52 +02:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
Attributes:
|
|
|
|
_sectname: The section to be filtered.
|
|
|
|
_optname: The option to be filtered.
|
|
|
|
"""
|
2014-09-28 11:27:52 +02:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
def __init__(self, sectname, optname=None):
|
|
|
|
"""Save decorator arguments.
|
2014-09-28 11:27:52 +02:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
Gets called on parse-time with the decorator arguments.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
See class attributes.
|
|
|
|
"""
|
|
|
|
if sectname not in configdata.DATA:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoSectionError(sectname)
|
2014-10-18 19:50:10 +02:00
|
|
|
if optname is not None and optname not in configdata.DATA[sectname]:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoOptionError(optname, sectname)
|
2014-10-18 19:50:10 +02:00
|
|
|
self._sectname = sectname
|
|
|
|
self._optname = optname
|
|
|
|
|
|
|
|
def __call__(self, func):
|
|
|
|
"""Register the command before running the 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.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@pyqtSlot(str, str)
|
|
|
|
@functools.wraps(func)
|
|
|
|
def wrapper(wrapper_self, sectname=None, optname=None):
|
|
|
|
# pylint: disable=missing-docstring
|
|
|
|
if sectname is None and optname is None:
|
|
|
|
# Called directly, not from a config change event.
|
|
|
|
return func(wrapper_self)
|
|
|
|
elif sectname != self._sectname:
|
|
|
|
return
|
|
|
|
elif self._optname is not None and optname != self._optname:
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
return func(wrapper_self)
|
|
|
|
|
|
|
|
return wrapper
|
2014-09-28 11:27:52 +02:00
|
|
|
|
|
|
|
|
2014-04-17 15:26:27 +02:00
|
|
|
def get(*args, **kwargs):
|
|
|
|
"""Convenience method to call get(...) of the config instance."""
|
2014-09-24 07:06:45 +02:00
|
|
|
return objreg.get('config').get(*args, **kwargs)
|
2014-05-05 17:56:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
def section(sect):
|
|
|
|
"""Get a config section from the global config."""
|
2014-09-24 07:06:45 +02:00
|
|
|
return objreg.get('config')[sect]
|
2014-04-17 15:26:27 +02:00
|
|
|
|
|
|
|
|
2014-09-28 00:27:22 +02:00
|
|
|
def init(args):
|
|
|
|
"""Initialize the config.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
args: The argparse namespace.
|
|
|
|
"""
|
2014-10-08 06:19:45 +02:00
|
|
|
confdir = standarddir.get(QStandardPaths.ConfigLocation, args)
|
2014-09-28 00:27:22 +02:00
|
|
|
try:
|
|
|
|
app = objreg.get('app')
|
|
|
|
config_obj = ConfigManager(confdir, 'qutebrowser.conf', app)
|
2015-01-16 07:10:12 +01:00
|
|
|
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
|
2014-09-28 00:27:22 +02:00
|
|
|
log.init.exception(e)
|
|
|
|
errstr = "Error while reading config:"
|
2014-12-17 11:17:18 +01:00
|
|
|
try:
|
|
|
|
errstr += "\n\n{} -> {}:".format(
|
|
|
|
e.section, e.option) # pylint: disable=no-member
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2014-09-28 00:27:22 +02:00
|
|
|
errstr += "\n{}".format(e)
|
|
|
|
msgbox = QMessageBox(QMessageBox.Critical,
|
|
|
|
"Error while reading config!", errstr)
|
|
|
|
msgbox.exec_()
|
|
|
|
# We didn't really initialize much so far, so we just quit hard.
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
objreg.register('config', config_obj)
|
|
|
|
try:
|
2014-12-13 17:28:50 +01:00
|
|
|
key_config = keyconf.KeyConfigParser(confdir, 'keys.conf')
|
2015-01-16 07:10:12 +01:00
|
|
|
except (keyconf.KeyConfigError, UnicodeDecodeError) as e:
|
2014-09-28 00:27:22 +02:00
|
|
|
log.init.exception(e)
|
|
|
|
errstr = "Error while reading key config:\n"
|
|
|
|
if e.lineno is not None:
|
|
|
|
errstr += "In line {}: ".format(e.lineno)
|
|
|
|
errstr += str(e)
|
|
|
|
msgbox = QMessageBox(QMessageBox.Critical,
|
|
|
|
"Error while reading key config!", errstr)
|
|
|
|
msgbox.exec_()
|
|
|
|
# We didn't really initialize much so far, so we just quit hard.
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
objreg.register('key-config', key_config)
|
2014-10-02 21:58:34 +02:00
|
|
|
|
2014-10-08 06:19:45 +02:00
|
|
|
datadir = standarddir.get(QStandardPaths.DataLocation, args)
|
2014-12-13 17:28:50 +01:00
|
|
|
state_config = ini.ReadWriteConfigParser(datadir, 'state')
|
2014-09-28 00:27:22 +02:00
|
|
|
objreg.register('state-config', state_config)
|
2014-09-28 11:27:52 +02:00
|
|
|
# We need to import this here because lineparser needs config.
|
2014-12-13 17:28:50 +01:00
|
|
|
from qutebrowser.config.parsers import line
|
|
|
|
command_history = line.LineConfigParser(datadir, 'cmd-history',
|
|
|
|
('completion', 'history-length'))
|
2014-09-28 00:27:22 +02:00
|
|
|
objreg.register('command-history', command_history)
|
|
|
|
|
|
|
|
|
2014-04-25 12:07:37 +02:00
|
|
|
class ConfigManager(QObject):
|
2014-02-24 07:17:17 +01:00
|
|
|
|
2014-03-09 20:10:57 +01:00
|
|
|
"""Configuration manager for qutebrowser.
|
2014-02-27 13:11:52 +01:00
|
|
|
|
2014-04-25 15:57:28 +02:00
|
|
|
Class attributes:
|
2014-05-01 20:51:07 +02:00
|
|
|
KEY_ESCAPE: Chars which need escaping when they occur as first char
|
|
|
|
in a line.
|
2014-04-25 15:57:28 +02:00
|
|
|
ESCAPE_CHAR: The char to be used for escaping
|
2014-11-27 07:07:28 +01:00
|
|
|
RENAMED_SECTIONS: A mapping of renamed sections, {'oldname': 'newname'}
|
|
|
|
RENAMED_OPTIONS: A mapping of renamed options,
|
|
|
|
{('section', 'oldname'): 'newname'}
|
2015-01-09 14:38:20 +01:00
|
|
|
DELETED_OPTIONS: A (section, option) list of deleted options.
|
2014-04-25 15:57:28 +02:00
|
|
|
|
2014-03-09 20:10:57 +01:00
|
|
|
Attributes:
|
2014-04-17 14:49:38 +02:00
|
|
|
sections: The configuration data as an OrderedDict.
|
2014-06-17 11:03:42 +02:00
|
|
|
_fname: The filename to be opened.
|
2014-03-09 20:10:57 +01:00
|
|
|
_configdir: The dictionary to read the config from and save it in.
|
2014-03-27 22:37:34 +01:00
|
|
|
_interpolation: An configparser.Interpolation object
|
|
|
|
_proxies: configparser.SectionProxy objects for sections.
|
2014-09-12 17:38:40 +02:00
|
|
|
_initialized: Whether the ConfigManager is fully initialized yet.
|
2014-04-10 18:01:16 +02:00
|
|
|
|
|
|
|
Signals:
|
2014-10-18 19:50:10 +02:00
|
|
|
changed: Emitted when a config option changed.
|
2014-04-10 23:30:45 +02:00
|
|
|
style_changed: When style caches need to be invalidated.
|
|
|
|
Args: the changed section and option.
|
2014-03-09 20:10:57 +01:00
|
|
|
"""
|
|
|
|
|
2014-05-01 20:51:07 +02:00
|
|
|
KEY_ESCAPE = r'\#['
|
2014-04-25 15:57:28 +02:00
|
|
|
ESCAPE_CHAR = '\\'
|
2014-11-27 07:07:28 +01:00
|
|
|
RENAMED_SECTIONS = {
|
|
|
|
'permissions': 'content'
|
|
|
|
}
|
|
|
|
RENAMED_OPTIONS = {
|
|
|
|
('colors', 'tab.fg.odd'): 'tabs.fg.odd',
|
|
|
|
('colors', 'tab.fg.even'): 'tabs.fg.even',
|
|
|
|
('colors', 'tab.fg.selected'): 'tabs.fg.selected',
|
|
|
|
('colors', 'tab.bg.odd'): 'tabs.bg.odd',
|
|
|
|
('colors', 'tab.bg.even'): 'tabs.bg.even',
|
|
|
|
('colors', 'tab.bg.selected'): 'tabs.bg.selected',
|
|
|
|
('colors', 'tab.bg.bar'): 'tabs.bg.bar',
|
|
|
|
('colors', 'tab.indicator.start'): 'tabs.indicator.start',
|
|
|
|
('colors', 'tab.indicator.stop'): 'tabs.indicator.stop',
|
|
|
|
('colors', 'tab.indicator.error'): 'tabs.indicator.error',
|
|
|
|
('colors', 'tab.indicator.system'): 'tabs.indicator.system',
|
|
|
|
}
|
2015-01-09 14:38:20 +01:00
|
|
|
DELETED_OPTIONS = [
|
|
|
|
('colors', 'tab.seperator'),
|
|
|
|
('colors', 'tabs.seperator'),
|
|
|
|
]
|
2014-04-25 15:57:28 +02:00
|
|
|
|
2014-10-18 19:50:10 +02:00
|
|
|
changed = pyqtSignal(str, str)
|
2014-04-10 23:30:45 +02:00
|
|
|
style_changed = pyqtSignal(str, str)
|
2014-04-10 18:01:16 +02:00
|
|
|
|
|
|
|
def __init__(self, configdir, fname, parent=None):
|
|
|
|
super().__init__(parent)
|
2014-09-12 17:38:40 +02:00
|
|
|
self._initialized = False
|
2014-04-17 17:44:27 +02:00
|
|
|
self.sections = configdata.DATA
|
2014-08-26 19:10:14 +02:00
|
|
|
self._interpolation = configparser.ExtendedInterpolation()
|
2014-03-27 22:37:34 +01:00
|
|
|
self._proxies = {}
|
2014-05-05 18:01:43 +02:00
|
|
|
for sectname in self.sections.keys():
|
|
|
|
self._proxies[sectname] = SectionProxy(self, sectname)
|
2014-10-02 22:45:47 +02:00
|
|
|
self._fname = fname
|
|
|
|
if configdir is None:
|
|
|
|
self._configdir = None
|
2014-12-17 13:27:03 +01:00
|
|
|
self._initialized = True
|
2014-10-02 22:45:47 +02:00
|
|
|
else:
|
|
|
|
self._configdir = configdir
|
2014-12-13 17:28:50 +01:00
|
|
|
parser = ini.ReadConfigParser(configdir, fname)
|
2014-10-02 22:45:47 +02:00
|
|
|
self._from_cp(parser)
|
2014-12-17 13:27:03 +01:00
|
|
|
self._initialized = True
|
|
|
|
self._validate_all()
|
2014-02-27 07:50:44 +01:00
|
|
|
|
2014-02-26 07:44:39 +01:00
|
|
|
def __getitem__(self, key):
|
|
|
|
"""Get a section from the config."""
|
2014-03-28 07:18:40 +01:00
|
|
|
return self._proxies[key]
|
2014-02-26 07:44:39 +01:00
|
|
|
|
2014-06-17 11:03:42 +02:00
|
|
|
def __repr__(self):
|
2014-09-26 15:48:24 +02:00
|
|
|
return utils.get_repr(self, fname=self._fname)
|
2014-06-17 11:03:42 +02:00
|
|
|
|
2014-02-26 22:36:06 +01:00
|
|
|
def __str__(self):
|
|
|
|
"""Get the whole config as a string."""
|
2014-02-27 21:05:51 +01:00
|
|
|
lines = configdata.FIRST_COMMENT.strip('\n').splitlines()
|
2014-05-05 18:01:43 +02:00
|
|
|
for sectname, sect in self.sections.items():
|
|
|
|
lines.append('\n[{}]'.format(sectname))
|
|
|
|
lines += self._str_section_desc(sectname)
|
|
|
|
lines += self._str_option_desc(sectname, sect)
|
|
|
|
lines += self._str_items(sect)
|
2014-03-09 20:13:40 +01:00
|
|
|
return '\n'.join(lines) + '\n'
|
2014-02-26 22:36:06 +01:00
|
|
|
|
2014-05-05 18:01:43 +02:00
|
|
|
def _str_section_desc(self, sectname):
|
|
|
|
"""Get the section description string for sectname."""
|
2014-09-09 21:40:16 +02:00
|
|
|
wrapper = textwrapper.TextWrapper()
|
2014-02-27 21:23:06 +01:00
|
|
|
lines = []
|
2014-05-05 18:01:43 +02:00
|
|
|
seclines = configdata.SECTION_DESC[sectname].splitlines()
|
2014-02-27 21:23:06 +01:00
|
|
|
for secline in seclines:
|
2014-04-22 08:42:47 +02:00
|
|
|
if 'http://' in secline or 'https://' in secline:
|
2014-02-27 21:23:06 +01:00
|
|
|
lines.append('# ' + secline)
|
|
|
|
else:
|
|
|
|
lines += wrapper.wrap(secline)
|
|
|
|
return lines
|
|
|
|
|
2014-05-05 18:01:43 +02:00
|
|
|
def _str_option_desc(self, sectname, sect):
|
|
|
|
"""Get the option description strings for sect/sectname."""
|
2014-09-09 21:40:16 +02:00
|
|
|
wrapper = textwrapper.TextWrapper(initial_indent='#' + ' ' * 5,
|
|
|
|
subsequent_indent='#' + ' ' * 5)
|
2014-02-27 21:23:06 +01:00
|
|
|
lines = []
|
2014-05-05 18:01:43 +02:00
|
|
|
if not getattr(sect, 'descriptions', None):
|
2014-02-27 22:13:26 +01:00
|
|
|
return lines
|
2014-05-05 18:01:43 +02:00
|
|
|
for optname, option in sect.items():
|
2014-02-27 22:29:25 +01:00
|
|
|
lines.append('#')
|
2014-02-27 22:13:26 +01:00
|
|
|
if option.typ.typestr is None:
|
|
|
|
typestr = ''
|
|
|
|
else:
|
|
|
|
typestr = ' ({})'.format(option.typ.typestr)
|
2014-04-25 16:53:23 +02:00
|
|
|
lines.append("# {}{}:".format(optname, typestr))
|
2014-02-27 21:23:06 +01:00
|
|
|
try:
|
2014-05-05 18:01:43 +02:00
|
|
|
desc = self.sections[sectname].descriptions[optname]
|
2014-09-16 08:20:19 +02:00
|
|
|
except KeyError:
|
|
|
|
log.misc.exception("No description for {}.{}!".format(
|
|
|
|
sectname, optname))
|
2014-02-27 21:23:06 +01:00
|
|
|
continue
|
|
|
|
for descline in desc.splitlines():
|
2014-02-27 22:29:25 +01:00
|
|
|
lines += wrapper.wrap(descline)
|
2014-02-27 22:13:26 +01:00
|
|
|
valid_values = option.typ.valid_values
|
2014-04-22 15:19:01 +02:00
|
|
|
if valid_values is not None:
|
2014-02-28 15:10:34 +01:00
|
|
|
if valid_values.descriptions:
|
|
|
|
for val in valid_values:
|
|
|
|
desc = valid_values.descriptions[val]
|
2014-04-25 16:53:23 +02:00
|
|
|
lines += wrapper.wrap(" {}: {}".format(val, desc))
|
2014-02-28 15:10:34 +01:00
|
|
|
else:
|
2014-04-25 16:53:23 +02:00
|
|
|
lines += wrapper.wrap("Valid values: {}".format(', '.join(
|
2014-02-27 22:29:25 +01:00
|
|
|
valid_values)))
|
2014-05-02 13:24:07 +02:00
|
|
|
lines += wrapper.wrap("Default: {}".format(
|
|
|
|
option.values['default']))
|
2014-02-27 21:23:06 +01:00
|
|
|
return lines
|
|
|
|
|
2014-05-05 18:01:43 +02:00
|
|
|
def _str_items(self, sect):
|
|
|
|
"""Get the option items as string for sect."""
|
2014-02-27 21:23:06 +01:00
|
|
|
lines = []
|
2014-05-05 18:01:43 +02:00
|
|
|
for optname, option in sect.items():
|
2014-09-02 21:54:07 +02:00
|
|
|
value = option.value(startlayer='conf')
|
2014-05-01 20:51:07 +02:00
|
|
|
for c in self.KEY_ESCAPE:
|
2014-04-25 15:57:28 +02:00
|
|
|
if optname.startswith(c):
|
|
|
|
optname = optname.replace(c, self.ESCAPE_CHAR + c, 1)
|
2014-05-11 21:55:41 +02:00
|
|
|
# configparser can't handle = in keys :(
|
|
|
|
optname = optname.replace('=', '<eq>')
|
2014-05-01 20:51:07 +02:00
|
|
|
keyval = '{} = {}'.format(optname, value)
|
2014-03-21 16:50:37 +01:00
|
|
|
lines.append(keyval)
|
2014-02-27 21:23:06 +01:00
|
|
|
return lines
|
|
|
|
|
2014-12-15 22:25:06 +01:00
|
|
|
def _get_real_sectname(self, cp, sectname):
|
|
|
|
"""Get an old or new section name based on a configparser.
|
|
|
|
|
|
|
|
This checks if sectname is in cp, and if not, migrates it if needed and
|
|
|
|
tries again.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
cp: The configparser to check.
|
|
|
|
sectname: The new section name.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The section name in the configparser as a string, or None if the
|
|
|
|
configparser doesn't contain the section.
|
|
|
|
"""
|
|
|
|
reverse_renamed_sections = {v: k for k, v in
|
|
|
|
self.RENAMED_SECTIONS.items()}
|
|
|
|
if sectname in reverse_renamed_sections:
|
|
|
|
old_sectname = reverse_renamed_sections[sectname]
|
|
|
|
else:
|
|
|
|
old_sectname = sectname
|
|
|
|
if old_sectname in cp:
|
|
|
|
return old_sectname
|
|
|
|
elif sectname in cp:
|
|
|
|
return sectname
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2014-04-17 19:02:58 +02:00
|
|
|
def _from_cp(self, cp):
|
|
|
|
"""Read the config from a configparser instance.
|
|
|
|
|
|
|
|
Args:
|
2014-04-21 21:11:01 +02:00
|
|
|
cp: The configparser instance to read the values from.
|
2014-04-17 19:02:58 +02:00
|
|
|
"""
|
2014-09-12 17:53:27 +02:00
|
|
|
for sectname in cp:
|
2014-11-27 07:07:28 +01:00
|
|
|
if sectname in self.RENAMED_SECTIONS:
|
|
|
|
sectname = self.RENAMED_SECTIONS[sectname]
|
2014-09-12 17:53:27 +02:00
|
|
|
if sectname is not 'DEFAULT' and sectname not in self.sections:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoSectionError(sectname)
|
2014-09-12 17:53:27 +02:00
|
|
|
for sectname in self.sections:
|
2014-12-15 22:25:06 +01:00
|
|
|
real_sectname = self._get_real_sectname(cp, sectname)
|
|
|
|
if real_sectname is None:
|
2014-04-17 19:02:58 +02:00
|
|
|
continue
|
2014-12-14 20:46:22 +01:00
|
|
|
for k, v in cp[real_sectname].items():
|
2014-04-25 15:57:28 +02:00
|
|
|
if k.startswith(self.ESCAPE_CHAR):
|
|
|
|
k = k[1:]
|
2015-01-09 14:38:20 +01:00
|
|
|
if (sectname, k) in self.DELETED_OPTIONS:
|
|
|
|
continue
|
|
|
|
elif (sectname, k) in self.RENAMED_OPTIONS:
|
2014-11-27 07:07:28 +01:00
|
|
|
k = self.RENAMED_OPTIONS[sectname, k]
|
2014-12-15 22:08:33 +01:00
|
|
|
self.set('conf', sectname, k, v, validate=False)
|
2014-12-08 18:50:53 +01:00
|
|
|
|
|
|
|
def _validate_all(self):
|
|
|
|
"""Validate all values set in self._from_cp."""
|
|
|
|
for sectname, sect in self.sections.items():
|
|
|
|
mapping = {key: val.value() for key, val in sect.values.items()}
|
|
|
|
for optname, opt in sect.items():
|
|
|
|
interpolated = self._interpolation.before_get(
|
|
|
|
self, sectname, optname, opt.value(), mapping)
|
2014-12-15 22:08:33 +01:00
|
|
|
try:
|
|
|
|
opt.typ.validate(interpolated)
|
2014-12-17 11:17:18 +01:00
|
|
|
except configexc.ValidationError as e:
|
2014-12-15 22:08:33 +01:00
|
|
|
e.section = sectname
|
|
|
|
e.option = optname
|
|
|
|
raise
|
2014-04-17 19:02:58 +02:00
|
|
|
|
2014-09-28 11:27:52 +02:00
|
|
|
def _changed(self, sectname, optname):
|
|
|
|
"""Notify other objects the config has changed."""
|
2014-09-12 17:20:47 +02:00
|
|
|
log.misc.debug("Config option changed: {} -> {}".format(
|
|
|
|
sectname, optname))
|
|
|
|
if sectname in ('colors', 'fonts'):
|
|
|
|
self.style_changed.emit(sectname, optname)
|
2014-10-18 19:50:10 +02:00
|
|
|
self.changed.emit(sectname, optname)
|
2014-09-12 17:20:47 +02:00
|
|
|
|
|
|
|
def _after_set(self, changed_sect, changed_opt):
|
|
|
|
"""Clean up caches and emit signals after an option has been set."""
|
|
|
|
self.get.cache_clear()
|
2014-09-28 11:27:52 +02:00
|
|
|
self._changed(changed_sect, changed_opt)
|
2014-09-12 17:20:47 +02:00
|
|
|
# Options in the same section and ${optname} interpolation.
|
|
|
|
for optname, option in self.sections[changed_sect].items():
|
|
|
|
if '${' + changed_opt + '}' in option.value():
|
2014-09-28 11:27:52 +02:00
|
|
|
self._changed(changed_sect, optname)
|
2014-09-12 17:20:47 +02:00
|
|
|
# Options in any section and ${sectname:optname} interpolation.
|
2014-09-15 22:01:13 +02:00
|
|
|
for sectname, sect in self.sections.items():
|
|
|
|
for optname, option in sect.items():
|
2014-09-12 17:20:47 +02:00
|
|
|
if ('${' + changed_sect + ':' + changed_opt + '}' in
|
|
|
|
option.value()):
|
2014-09-28 11:27:52 +02:00
|
|
|
self._changed(sectname, optname)
|
2014-09-12 17:20:47 +02:00
|
|
|
|
2014-07-10 22:32:41 +02:00
|
|
|
def items(self, sectname, raw=True):
|
|
|
|
"""Get a list of (optname, value) tuples for a section.
|
|
|
|
|
|
|
|
Implemented for configparser interpolation compatbility.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sectname: The name of the section to get.
|
|
|
|
raw: Whether to get raw values. Note this parameter only exists
|
|
|
|
for ConfigParser compatibility and raw=False is not supported.
|
|
|
|
"""
|
|
|
|
items = []
|
|
|
|
if not raw:
|
|
|
|
raise ValueError("items() with raw=True is not implemented!")
|
|
|
|
for optname, option in self.sections[sectname].items():
|
2014-09-02 21:54:07 +02:00
|
|
|
items.append((optname, option.value()))
|
2014-07-10 22:32:41 +02:00
|
|
|
return items
|
|
|
|
|
2014-05-05 18:37:40 +02:00
|
|
|
def has_option(self, sectname, optname):
|
2014-04-10 12:37:49 +02:00
|
|
|
"""Check if option exists in section.
|
2014-04-10 09:52:05 +02:00
|
|
|
|
2014-04-17 17:44:27 +02:00
|
|
|
Args:
|
2014-05-05 18:01:43 +02:00
|
|
|
sectname: The section name.
|
2014-05-05 18:37:40 +02:00
|
|
|
optname: The option name
|
2014-04-10 12:37:49 +02:00
|
|
|
|
|
|
|
Return:
|
|
|
|
True if the option and section exist, False otherwise.
|
2014-04-10 12:24:41 +02:00
|
|
|
"""
|
2014-05-05 18:01:43 +02:00
|
|
|
if sectname not in self.sections:
|
2014-04-10 09:52:05 +02:00
|
|
|
return False
|
2014-05-05 18:37:40 +02:00
|
|
|
return optname in self.sections[sectname]
|
2014-04-07 17:53:57 +02:00
|
|
|
|
2014-05-05 18:37:40 +02:00
|
|
|
def remove_option(self, sectname, optname):
|
2014-04-10 12:37:49 +02:00
|
|
|
"""Remove an option.
|
|
|
|
|
2014-04-17 17:44:27 +02:00
|
|
|
Args:
|
2014-05-05 18:01:43 +02:00
|
|
|
sectname: The section where to remove an option.
|
2014-05-05 18:37:40 +02:00
|
|
|
optname: The option name to remove.
|
2014-04-10 12:37:49 +02:00
|
|
|
|
|
|
|
Return:
|
|
|
|
True if the option existed, False otherwise.
|
|
|
|
"""
|
|
|
|
try:
|
2014-05-05 18:01:43 +02:00
|
|
|
sectdict = self.sections[sectname]
|
2014-04-10 12:37:49 +02:00
|
|
|
except KeyError:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoSectionError(sectname)
|
2014-05-05 18:37:40 +02:00
|
|
|
optname = self.optionxform(optname)
|
|
|
|
existed = optname in sectdict
|
2014-04-10 12:37:49 +02:00
|
|
|
if existed:
|
2014-05-05 18:37:40 +02:00
|
|
|
del sectdict[optname]
|
2014-08-27 20:16:04 +02:00
|
|
|
self.get.cache_clear()
|
2014-04-10 12:37:49 +02:00
|
|
|
return existed
|
|
|
|
|
2014-08-27 20:16:04 +02:00
|
|
|
@functools.lru_cache()
|
2014-05-05 18:37:40 +02:00
|
|
|
def get(self, sectname, optname, raw=False, transformed=True):
|
2014-03-28 07:18:40 +01:00
|
|
|
"""Get the value from a section/option.
|
|
|
|
|
2014-04-17 17:44:27 +02:00
|
|
|
Args:
|
2014-05-05 18:01:43 +02:00
|
|
|
sectname: The section to get the option from.
|
2014-05-05 18:37:40 +02:00
|
|
|
optname: The option name
|
2014-04-02 16:47:21 +02:00
|
|
|
raw: Whether to get the uninterpolated, untransformed value.
|
2014-05-01 20:06:34 +02:00
|
|
|
transformed: Whether the value should be transformed.
|
2014-04-17 17:44:27 +02:00
|
|
|
|
|
|
|
Return:
|
|
|
|
The value of the option.
|
2014-03-28 07:18:40 +01:00
|
|
|
"""
|
2014-09-12 17:38:40 +02:00
|
|
|
if not self._initialized:
|
|
|
|
raise Exception("get got called before initialisation was "
|
|
|
|
"complete!")
|
2014-02-26 07:44:39 +01:00
|
|
|
try:
|
2014-05-05 18:01:43 +02:00
|
|
|
sect = self.sections[sectname]
|
2014-04-10 09:52:05 +02:00
|
|
|
except KeyError:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoSectionError(sectname)
|
2014-04-10 09:52:05 +02:00
|
|
|
try:
|
2014-05-05 18:37:40 +02:00
|
|
|
val = sect[optname]
|
2014-02-26 07:44:39 +01:00
|
|
|
except KeyError:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoOptionError(optname, sectname)
|
2014-04-10 09:52:05 +02:00
|
|
|
if raw:
|
2014-09-02 21:54:07 +02:00
|
|
|
return val.value()
|
|
|
|
mapping = {key: val.value() for key, val in sect.values.items()}
|
2014-05-05 18:37:40 +02:00
|
|
|
newval = self._interpolation.before_get(self, sectname, optname,
|
2014-09-02 21:54:07 +02:00
|
|
|
val.value(), mapping)
|
2014-05-01 20:06:34 +02:00
|
|
|
if transformed:
|
|
|
|
newval = val.typ.transform(newval)
|
2014-04-10 09:52:05 +02:00
|
|
|
return newval
|
2014-02-26 07:44:39 +01:00
|
|
|
|
2014-05-02 10:20:08 +02:00
|
|
|
@cmdutils.register(name='set', instance='config',
|
2014-07-29 00:37:32 +02:00
|
|
|
completion=[Completion.section, Completion.option,
|
|
|
|
Completion.value])
|
2014-10-09 06:33:24 +02:00
|
|
|
def set_command(self, win_id: {'special': 'win_id'},
|
2014-12-01 23:09:01 +01:00
|
|
|
sectname: {'name': 'section'}=None,
|
|
|
|
optname: {'name': 'option'}=None, value=None, temp=False):
|
2014-04-10 12:01:02 +02:00
|
|
|
"""Set an option.
|
|
|
|
|
2014-09-15 06:24:15 +02:00
|
|
|
If the option name ends with '?', the value of the option is shown
|
|
|
|
instead.
|
|
|
|
|
2015-01-18 15:34:33 +01:00
|
|
|
If the option name ends with '!' and it is a boolean value, toggle it.
|
|
|
|
|
2014-07-16 20:09:41 +02:00
|
|
|
//
|
|
|
|
|
2014-04-10 12:01:02 +02:00
|
|
|
Wrapper for self.set() to output exceptions in the status bar.
|
2014-08-03 00:33:39 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
sectname: The section where the option is in.
|
|
|
|
optname: The name of the option.
|
|
|
|
value: The value to set.
|
2014-09-13 00:22:27 +02:00
|
|
|
temp: Set value temporarily.
|
2014-04-10 12:01:02 +02:00
|
|
|
"""
|
2014-12-01 23:09:01 +01:00
|
|
|
if sectname is not None and optname is None:
|
|
|
|
raise cmdexc.CommandError(
|
|
|
|
"set: Either both section and option have to be given, or "
|
|
|
|
"neither!")
|
|
|
|
if sectname is None and optname is None:
|
|
|
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
|
|
|
window=win_id)
|
|
|
|
tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
|
|
|
|
return
|
2014-04-10 12:01:02 +02:00
|
|
|
try:
|
2014-09-15 06:24:15 +02:00
|
|
|
if optname.endswith('?'):
|
|
|
|
val = self.get(sectname, optname[:-1], transformed=False)
|
2014-09-28 22:13:14 +02:00
|
|
|
message.info(win_id, "{} {} = {}".format(
|
|
|
|
sectname, optname[:-1], val), immediately=True)
|
2015-01-18 15:34:33 +01:00
|
|
|
elif optname.endswith('!'):
|
|
|
|
val = self.get(sectname, optname[:-1])
|
|
|
|
layer = 'temp' if temp else 'conf'
|
2015-01-21 15:24:05 +01:00
|
|
|
if isinstance(val, bool):
|
2015-01-18 15:34:33 +01:00
|
|
|
self.set(layer, sectname, optname[:-1], str(not val))
|
|
|
|
else:
|
2015-01-21 15:24:05 +01:00
|
|
|
raise cmdexc.CommandError("set: Attempted inversion of non-boolean value.")
|
2014-09-15 06:24:15 +02:00
|
|
|
else:
|
|
|
|
if value is None:
|
|
|
|
raise cmdexc.CommandError("set: The following arguments "
|
|
|
|
"are required: value")
|
|
|
|
layer = 'temp' if temp else 'conf'
|
|
|
|
self.set(layer, sectname, optname, value)
|
2014-12-17 11:17:18 +01:00
|
|
|
except (configexc.Error, configparser.Error) as e:
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.CommandError("set: {} - {}".format(
|
|
|
|
e.__class__.__name__, e))
|
2014-04-10 12:01:02 +02:00
|
|
|
|
2014-12-08 18:50:53 +01:00
|
|
|
def set(self, layer, sectname, optname, value, validate=True):
|
2014-04-10 18:01:16 +02:00
|
|
|
"""Set an option.
|
|
|
|
|
|
|
|
Args:
|
2014-04-17 17:44:27 +02:00
|
|
|
layer: A layer name as string (conf/temp/default).
|
2014-05-05 18:01:43 +02:00
|
|
|
sectname: The name of the section to change.
|
2014-05-05 18:37:40 +02:00
|
|
|
optname: The name of the option to change.
|
2014-04-10 18:01:16 +02:00
|
|
|
value: The new value.
|
2014-12-08 18:50:53 +01:00
|
|
|
validate: Whether to validate the value immediately.
|
2014-04-10 18:01:16 +02:00
|
|
|
"""
|
2014-07-10 22:38:09 +02:00
|
|
|
try:
|
|
|
|
value = self._interpolation.before_set(self, sectname, optname,
|
|
|
|
value)
|
|
|
|
except ValueError as e:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.InterpolationSyntaxError(optname, sectname, str(e))
|
2014-04-10 07:09:12 +02:00
|
|
|
try:
|
2014-05-05 18:01:43 +02:00
|
|
|
sect = self.sections[sectname]
|
2014-04-10 07:09:12 +02:00
|
|
|
except KeyError:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoSectionError(sectname)
|
2014-09-02 21:54:07 +02:00
|
|
|
mapping = {key: val.value() for key, val in sect.values.items()}
|
2014-12-08 18:50:53 +01:00
|
|
|
if validate:
|
|
|
|
interpolated = self._interpolation.before_get(
|
|
|
|
self, sectname, optname, value, mapping)
|
|
|
|
else:
|
|
|
|
interpolated = None
|
2014-04-10 12:03:42 +02:00
|
|
|
try:
|
2014-05-05 18:37:40 +02:00
|
|
|
sect.setv(layer, optname, value, interpolated)
|
2014-04-10 12:03:42 +02:00
|
|
|
except KeyError:
|
2014-12-17 11:17:18 +01:00
|
|
|
raise configexc.NoOptionError(optname, sectname)
|
2014-04-10 18:01:16 +02:00
|
|
|
else:
|
2014-09-12 17:38:40 +02:00
|
|
|
if self._initialized:
|
|
|
|
self._after_set(sectname, optname)
|
2014-04-09 22:44:07 +02:00
|
|
|
|
2014-12-10 18:00:49 +01:00
|
|
|
@cmdutils.register(instance='config', name='save')
|
|
|
|
def save_command(self):
|
|
|
|
"""Save the config file."""
|
|
|
|
try:
|
|
|
|
self.save()
|
|
|
|
except OSError as e:
|
|
|
|
raise cmdexc.CommandError("Could not save config: {}".format(e))
|
|
|
|
|
2014-02-26 07:44:39 +01:00
|
|
|
def save(self):
|
2014-02-26 09:18:27 +01:00
|
|
|
"""Save the config file."""
|
2014-10-02 22:45:47 +02:00
|
|
|
if self._configdir is None:
|
|
|
|
return
|
2014-10-02 22:51:59 +02:00
|
|
|
configfile = os.path.join(self._configdir, self._fname)
|
2014-10-02 22:45:47 +02:00
|
|
|
log.destroy.debug("Saving config to {}".format(configfile))
|
2014-12-08 23:42:26 +01:00
|
|
|
with qtutils.savefile_open(configfile) as f:
|
2014-03-09 20:10:57 +01:00
|
|
|
f.write(str(self))
|
2014-02-26 07:44:39 +01:00
|
|
|
|
|
|
|
def dump_userconfig(self):
|
2014-02-26 09:18:27 +01:00
|
|
|
"""Get the part of the config which was changed by the user.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
The changed config part as string.
|
|
|
|
"""
|
2014-04-10 07:37:13 +02:00
|
|
|
lines = []
|
2014-05-05 18:01:43 +02:00
|
|
|
for sectname, sect in self.sections.items():
|
|
|
|
changed = sect.dump_userconfig()
|
2014-04-17 12:35:46 +02:00
|
|
|
if changed:
|
2014-05-05 18:01:43 +02:00
|
|
|
lines.append('[{}]'.format(sectname))
|
2014-04-17 12:35:46 +02:00
|
|
|
lines += ['{} = {}'.format(k, v) for k, v in changed]
|
2014-07-29 22:51:32 +02:00
|
|
|
if not lines:
|
|
|
|
lines = ['<Default configuration>']
|
2014-04-10 07:37:13 +02:00
|
|
|
return '\n'.join(lines)
|
2014-02-26 07:44:39 +01:00
|
|
|
|
2014-04-07 16:51:14 +02:00
|
|
|
def optionxform(self, val):
|
|
|
|
"""Implemented to be compatible with ConfigParser interpolation."""
|
|
|
|
return val
|
|
|
|
|
2014-02-26 07:44:39 +01:00
|
|
|
|
2014-08-26 19:10:14 +02:00
|
|
|
class SectionProxy(collections.abc.MutableMapping):
|
2014-04-07 17:20:14 +02:00
|
|
|
|
2014-04-10 12:37:49 +02:00
|
|
|
"""A proxy for a single section from a config.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
_conf: The Config object.
|
|
|
|
_name: The section name.
|
|
|
|
"""
|
2014-03-27 22:37:34 +01:00
|
|
|
|
2014-04-10 12:37:49 +02:00
|
|
|
def __init__(self, conf, name):
|
|
|
|
"""Create a view on a section.
|
|
|
|
|
2014-04-17 17:44:27 +02:00
|
|
|
Args:
|
2014-04-10 12:37:49 +02:00
|
|
|
conf: The Config object.
|
|
|
|
name: The section name.
|
|
|
|
"""
|
2014-09-02 21:54:07 +02:00
|
|
|
self.conf = conf
|
|
|
|
self.name = name
|
2014-04-10 12:37:49 +02:00
|
|
|
|
|
|
|
def __repr__(self):
|
2014-09-26 15:48:24 +02:00
|
|
|
return utils.get_repr(self, name=self.name)
|
2014-04-10 12:37:49 +02:00
|
|
|
|
2014-03-27 22:37:34 +01:00
|
|
|
def __getitem__(self, key):
|
2014-09-02 21:54:07 +02:00
|
|
|
if not self.conf.has_option(self.name, key):
|
2014-04-10 12:37:49 +02:00
|
|
|
raise KeyError(key)
|
2014-09-02 21:54:07 +02:00
|
|
|
return self.conf.get(self.name, key)
|
2014-03-27 22:37:34 +01:00
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
2014-09-02 21:54:07 +02:00
|
|
|
return self.conf.set('conf', self.name, key, value)
|
2014-03-27 22:37:34 +01:00
|
|
|
|
|
|
|
def __delitem__(self, key):
|
2014-09-02 21:54:07 +02:00
|
|
|
if not (self.conf.has_option(self.name, key) and
|
|
|
|
self.conf.remove_option(self.name, key)):
|
2014-04-10 12:37:49 +02:00
|
|
|
raise KeyError(key)
|
2014-03-27 22:37:34 +01:00
|
|
|
|
|
|
|
def __contains__(self, key):
|
2014-09-02 21:54:07 +02:00
|
|
|
return self.conf.has_option(self.name, key)
|
2014-03-27 22:37:34 +01:00
|
|
|
|
2014-04-10 12:37:49 +02:00
|
|
|
def __len__(self):
|
|
|
|
return len(self._options())
|
2014-03-27 22:37:34 +01:00
|
|
|
|
2014-04-10 12:37:49 +02:00
|
|
|
def __iter__(self):
|
|
|
|
return self._options().__iter__()
|
2014-03-27 22:37:34 +01:00
|
|
|
|
2014-04-10 12:37:49 +02:00
|
|
|
def _options(self):
|
|
|
|
"""Get the option keys from this section."""
|
2014-09-02 21:54:07 +02:00
|
|
|
return self.conf.sections[self.name].keys()
|
2014-03-27 22:37:34 +01:00
|
|
|
|
2014-08-04 03:47:09 +02:00
|
|
|
def get(self, optname, *, raw=False): # pylint: disable=arguments-differ
|
2014-04-10 12:37:49 +02:00
|
|
|
"""Get a value from this section.
|
2014-03-27 22:37:34 +01:00
|
|
|
|
2014-04-10 14:40:02 +02:00
|
|
|
We deliberately don't support the default argument here, but have a raw
|
|
|
|
argument instead.
|
|
|
|
|
2014-04-17 17:44:27 +02:00
|
|
|
Args:
|
2014-05-05 18:37:40 +02:00
|
|
|
optname: The option name to get.
|
2014-04-10 12:37:49 +02:00
|
|
|
raw: Whether to get a raw value or not.
|
|
|
|
"""
|
2014-09-02 21:54:07 +02:00
|
|
|
return self.conf.get(self.name, optname, raw=raw)
|