2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2014-02-06 14:01:23 +01:00
|
|
|
# Copyright 2014 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/>.
|
|
|
|
|
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-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-08-26 19:10:14 +02:00
|
|
|
import collections.abc
|
2014-01-27 21:42:00 +01:00
|
|
|
|
2014-09-23 22:28:28 +02:00
|
|
|
from PyQt5.QtCore import pyqtSignal, QObject
|
2014-04-10 18:01:16 +02:00
|
|
|
|
2014-08-26 19:10:14 +02:00
|
|
|
from qutebrowser.utils import log
|
2014-09-09 21:40:16 +02:00
|
|
|
from qutebrowser.config import configdata, iniparsers, configtypes, textwrapper
|
2014-08-26 20:48:39 +02:00
|
|
|
from qutebrowser.commands import cmdexc, cmdutils
|
2014-09-26 15:48:24 +02:00
|
|
|
from qutebrowser.utils import message, objreg, utils
|
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-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-04-16 16:16:17 +02:00
|
|
|
class NoSectionError(configparser.NoSectionError):
|
|
|
|
|
|
|
|
"""Exception raised when a section was not found."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NoOptionError(configparser.NoOptionError):
|
|
|
|
|
|
|
|
"""Exception raised when an option was not found."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-07-10 22:38:09 +02:00
|
|
|
class InterpolationSyntaxError(ValueError):
|
|
|
|
|
|
|
|
"""Exception raised when configparser interpolation raised an error."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-09-12 17:53:27 +02:00
|
|
|
class UnknownSectionError(Exception):
|
|
|
|
|
|
|
|
"""Exception raised when there was an unknwon section in the config."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
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-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-10 00:37:35 +01:00
|
|
|
_configparser: A ReadConfigParser instance to load the config.
|
2014-03-09 20:10:57 +01:00
|
|
|
_configdir: The dictionary to read the config from and save it in.
|
|
|
|
_configfile: The config file path.
|
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:
|
|
|
|
changed: Gets emitted when the config has changed.
|
2014-04-16 09:21:27 +02:00
|
|
|
Args: the changed section, option and new value.
|
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-04-17 11:39:25 +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._configparser = iniparsers.ReadConfigParser(configdir, fname)
|
2014-03-09 20:10:57 +01:00
|
|
|
self._configfile = os.path.join(configdir, fname)
|
|
|
|
self._configdir = configdir
|
2014-06-17 11:03:42 +02:00
|
|
|
self._fname = fname
|
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-04-17 19:02:58 +02:00
|
|
|
self._from_cp(self._configparser)
|
2014-09-15 22:01:13 +02:00
|
|
|
self._initialized = True
|
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-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:
|
|
|
|
if sectname is not 'DEFAULT' and sectname not in self.sections:
|
|
|
|
raise UnknownSectionError("Unknown section '{}'!".format(
|
|
|
|
sectname))
|
|
|
|
for sectname in self.sections:
|
2014-05-05 18:01:43 +02:00
|
|
|
if sectname not in cp:
|
2014-04-17 19:02:58 +02:00
|
|
|
continue
|
2014-05-05 18:01:43 +02:00
|
|
|
for k, v in cp[sectname].items():
|
2014-04-25 15:57:28 +02:00
|
|
|
if k.startswith(self.ESCAPE_CHAR):
|
|
|
|
k = k[1:]
|
2014-05-11 21:55:41 +02:00
|
|
|
# configparser can't handle = in keys :(
|
|
|
|
k = k.replace('<eq>', '=')
|
2014-04-21 22:29:57 +02:00
|
|
|
try:
|
2014-05-05 18:01:43 +02:00
|
|
|
self.set('conf', sectname, k, v)
|
2014-08-26 21:41:41 +02:00
|
|
|
except configtypes.ValidationError as e:
|
2014-05-05 18:01:43 +02:00
|
|
|
e.section = sectname
|
2014-04-21 22:29:57 +02:00
|
|
|
e.option = k
|
|
|
|
raise
|
2014-04-17 19:02:58 +02:00
|
|
|
|
2014-09-12 17:20:47 +02:00
|
|
|
def _emit_changed(self, sectname, optname):
|
|
|
|
"""Emit the appropriate signals for a changed config option."""
|
|
|
|
log.misc.debug("Config option changed: {} -> {}".format(
|
|
|
|
sectname, optname))
|
|
|
|
if sectname in ('colors', 'fonts'):
|
|
|
|
self.style_changed.emit(sectname, optname)
|
|
|
|
self.changed.emit(sectname, optname)
|
|
|
|
|
|
|
|
def _after_set(self, changed_sect, changed_opt):
|
|
|
|
"""Clean up caches and emit signals after an option has been set."""
|
|
|
|
self.get.cache_clear()
|
|
|
|
self._emit_changed(changed_sect, changed_opt)
|
|
|
|
# Options in the same section and ${optname} interpolation.
|
|
|
|
for optname, option in self.sections[changed_sect].items():
|
|
|
|
if '${' + changed_opt + '}' in option.value():
|
|
|
|
self._emit_changed(changed_sect, optname)
|
|
|
|
# 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()):
|
|
|
|
self._emit_changed(sectname, optname)
|
|
|
|
|
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-05-05 18:01:43 +02:00
|
|
|
raise 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-05-05 18:01:43 +02:00
|
|
|
raise 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-05-05 18:37:40 +02:00
|
|
|
raise 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-09-15 00:03:59 +02:00
|
|
|
def set_command(self, sectname: {'name': 'section'},
|
2014-09-15 06:24:15 +02:00
|
|
|
optname: {'name': 'option'}, 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.
|
|
|
|
|
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
|
|
|
"""
|
|
|
|
try:
|
2014-09-15 06:24:15 +02:00
|
|
|
if optname.endswith('?'):
|
|
|
|
val = self.get(sectname, optname[:-1], transformed=False)
|
|
|
|
message.info("{} {} = {}".format(sectname, optname[:-1], val),
|
|
|
|
immediately=True)
|
|
|
|
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-08-26 21:41:41 +02:00
|
|
|
except (NoOptionError, NoSectionError, configtypes.ValidationError,
|
2014-05-02 12:27:06 +02:00
|
|
|
ValueError) 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-05-05 18:37:40 +02:00
|
|
|
def set(self, layer, sectname, optname, value):
|
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.
|
|
|
|
|
|
|
|
Raise:
|
|
|
|
NoSectionError: If the specified section doesn't exist.
|
|
|
|
NoOptionError: If the specified option doesn't exist.
|
|
|
|
|
|
|
|
Emit:
|
|
|
|
changed: If the config was changed.
|
2014-04-10 23:30:45 +02:00
|
|
|
style_changed: When style caches need to be invalidated.
|
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:
|
|
|
|
raise InterpolationSyntaxError(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-05-05 18:01:43 +02:00
|
|
|
raise NoSectionError(sectname)
|
2014-09-02 21:54:07 +02:00
|
|
|
mapping = {key: val.value() for key, val in sect.values.items()}
|
2014-05-05 18:37:40 +02:00
|
|
|
interpolated = self._interpolation.before_get(self, sectname, optname,
|
2014-04-17 19:02:58 +02:00
|
|
|
value, mapping)
|
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-05-05 18:37:40 +02:00
|
|
|
raise 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-04-15 17:28:14 +02:00
|
|
|
@cmdutils.register(instance='config')
|
2014-02-26 07:44:39 +01:00
|
|
|
def save(self):
|
2014-02-26 09:18:27 +01:00
|
|
|
"""Save the config file."""
|
2014-03-09 20:10:57 +01:00
|
|
|
if not os.path.exists(self._configdir):
|
|
|
|
os.makedirs(self._configdir, 0o755)
|
2014-05-23 16:11:55 +02:00
|
|
|
log.destroy.debug("Saving config to {}".format(self._configfile))
|
2014-05-02 11:15:38 +02:00
|
|
|
with open(self._configfile, 'w', encoding='utf-8') 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)
|