qutebrowser/qutebrowser/config/config.py

342 lines
12 KiB
Python
Raw Normal View History

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-01-27 21:42:00 +01:00
import logging
2014-02-27 07:18:36 +01:00
import textwrap
2014-03-27 22:37:34 +01:00
import configparser
2014-04-09 22:44:07 +02:00
from configparser import ConfigParser, ExtendedInterpolation, NoSectionError
2014-01-27 21:42:00 +01:00
2014-02-26 09:18:27 +01:00
#from qutebrowser.utils.misc import read_file
2014-02-27 18:46:30 +01:00
import qutebrowser.config.configdata as configdata
2014-03-09 19:25:15 +01:00
import qutebrowser.commands.utils as cmdutils
2014-04-09 20:47:24 +02:00
import qutebrowser.utils.message as message
2014-02-18 10:07:52 +01:00
2014-01-28 12:21:00 +01:00
config = None
2014-02-18 11:57:35 +01:00
state = None
2014-01-28 12:21:00 +01:00
2014-02-18 14:34:46 +01:00
# Special value for an unset fallback, so None can be passed as fallback.
_UNSET = object()
2014-01-28 23:04:02 +01:00
def init(configdir):
2014-02-19 10:58:32 +01:00
"""Initialize the global objects based on the config in configdir.
Args:
configdir: The directory where the configs are stored in.
2014-02-19 10:58:32 +01:00
"""
global config, state
logging.debug("Config init, configdir {}".format(configdir))
#config = Config(configdir, 'qutebrowser.conf',
2014-02-26 09:18:27 +01:00
# read_file('qutebrowser.conf'))
config = Config(configdir, 'qutebrowser.conf')
state = ReadWriteConfigParser(configdir, 'state')
2014-02-10 07:03:51 +01:00
2014-02-18 14:21:39 +01:00
class Config:
2014-02-24 07:17:17 +01:00
"""Configuration manager for qutebrowser.
2014-02-27 13:11:52 +01:00
Attributes:
config: The configuration data as an OrderedDict.
2014-03-10 00:37:35 +01:00
_configparser: A ReadConfigParser instance to load the config.
_wrapper_args: A dict with the default kwargs for the config wrappers.
_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.
"""
def __init__(self, configdir, fname):
2014-02-27 18:46:30 +01:00
self.config = configdata.configdata()
self._configparser = ReadConfigParser(configdir, fname)
self._configfile = os.path.join(configdir, fname)
2014-02-27 21:23:06 +01:00
self._wrapper_args = {
'width': 72,
'replace_whitespace': False,
'break_long_words': False,
'break_on_hyphens': False,
}
self._configdir = configdir
2014-03-27 22:37:34 +01:00
self._interpolation = ExtendedInterpolation()
self._proxies = {}
2014-03-10 00:37:35 +01:00
for secname, section in self.config.items():
2014-03-27 22:37:34 +01:00
self._proxies[secname] = SectionProxy(self, secname)
2014-03-10 00:37:35 +01:00
try:
section.from_cp(self._configparser[secname])
except KeyError:
pass
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
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()
for secname, section in self.config.items():
2014-02-27 21:05:51 +01:00
lines.append('\n[{}]'.format(secname))
2014-02-27 21:23:06 +01:00
lines += self._str_section_desc(secname)
lines += self._str_option_desc(secname, section)
lines += self._str_items(section)
2014-03-09 20:13:40 +01:00
return '\n'.join(lines) + '\n'
2014-02-27 21:23:06 +01:00
def _str_section_desc(self, secname):
2014-02-27 23:21:21 +01:00
"""Get the section description string for secname."""
2014-02-27 21:23:06 +01:00
wrapper = textwrap.TextWrapper(initial_indent='# ',
subsequent_indent='# ',
**self._wrapper_args)
lines = []
seclines = configdata.SECTION_DESC[secname].splitlines()
for secline in seclines:
if 'http://' in secline:
lines.append('# ' + secline)
else:
lines += wrapper.wrap(secline)
return lines
def _str_option_desc(self, secname, section):
2014-02-27 23:21:21 +01:00
"""Get the option description strings for section/secname."""
2014-02-27 21:23:06 +01:00
wrapper = textwrap.TextWrapper(initial_indent='#' + ' ' * 5,
subsequent_indent='#' + ' ' * 5,
**self._wrapper_args)
lines = []
2014-04-07 17:05:51 +02:00
if not getattr(section, 'descriptions', None):
2014-02-27 22:13:26 +01:00
return lines
2014-02-27 21:23:06 +01:00
for optname, option in section.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)
lines.append('# {}{}:'.format(optname, typestr))
2014-02-27 21:23:06 +01:00
try:
desc = self.config[secname].descriptions[optname]
except KeyError:
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-02-28 15:10:34 +01:00
if valid_values is not None and valid_values.show:
if valid_values.descriptions:
for val in valid_values:
desc = valid_values.descriptions[val]
lines += wrapper.wrap(' {}: {}'.format(val, desc))
else:
2014-02-27 22:29:25 +01:00
lines += wrapper.wrap('Valid values: {}'.format(', '.join(
valid_values)))
2014-04-02 16:47:21 +02:00
lines += wrapper.wrap('Default: {}'.format(option.default))
2014-02-27 21:23:06 +01:00
return lines
def _str_items(self, section):
2014-02-27 23:21:21 +01:00
"""Get the option items as string for section."""
2014-02-27 21:23:06 +01:00
lines = []
for optname, option in section.items():
keyval = '{} = {}'.format(optname, option)
2014-03-21 16:50:37 +01:00
lines.append(keyval)
2014-02-27 21:23:06 +01:00
return lines
2014-04-09 17:57:00 +02:00
def has_option(self, section, option):
"""Return True if option is in section."""
2014-04-07 17:53:57 +02:00
return option in self.config[section]
2014-04-10 06:58:58 +02:00
@cmdutils.register(name='get', instance='config',
completion=['section', 'option'])
def get_wrapper(self, section, option):
2014-04-09 20:47:24 +02:00
"""Get the value from a section/option.
2014-04-10 06:58:58 +02:00
Wrapper for the get-command to output the value in the status bar
2014-04-09 17:54:41 +02:00
Arguments:
2014-04-10 06:58:58 +02:00
section: Section to get the value from
option: The option to get.
2014-04-09 17:54:41 +02:00
Return:
2014-04-10 06:58:58 +02:00
The value of the option.
2014-04-09 17:54:41 +02:00
"""
2014-04-10 06:58:58 +02:00
val = self.get(section, option)
message.info("{} {} = {}".format(section, option, val))
2014-04-09 17:54:41 +02:00
2014-03-28 07:18:40 +01:00
def get(self, section, option, fallback=_UNSET, raw=False):
"""Get the value from a section/option.
Arguments:
section: The section to get the option from.
option: The option name
fallback: A fallback value.
2014-04-02 16:47:21 +02:00
raw: Whether to get the uninterpolated, untransformed value.
2014-03-28 07:18:40 +01:00
"""
logging.debug("getting {} -> {}".format(section, option))
2014-02-26 07:44:39 +01:00
try:
val = self.config[section][option]
except KeyError:
if fallback is _UNSET:
raise
else:
return fallback
else:
2014-03-28 07:18:40 +01:00
if raw:
2014-04-07 16:51:14 +02:00
return val.value
2014-04-07 17:20:14 +02:00
mapping = {key: val.value
for key, val in self.config[section].values.items()}
newval = self._interpolation.before_get(self, section, option,
val.value, mapping)
2014-03-28 07:18:40 +01:00
logging.debug("interpolated val: {}".format(newval))
newval = val.typ.transform(newval)
return newval
2014-02-26 07:44:39 +01:00
2014-04-10 06:58:58 +02:00
@cmdutils.register(instance='config', completion=['section', 'option'])
2014-04-09 22:44:07 +02:00
def set(self, section, option, value):
2014-04-10 06:58:58 +02:00
# FIXME completion for values
2014-04-09 22:44:07 +02:00
"""Set an option."""
if value:
value = self._interpolation.before_set(self, section, option,
value)
if not section or section == self.default_section:
sectdict = self._defaults
else:
try:
sectdict = self._sections[section]
except KeyError:
raise NoSectionError(section)
sectdict[self.optionxform(option)] = value
2014-02-26 07:44:39 +01:00
def save(self):
2014-02-26 09:18:27 +01:00
"""Save the config file."""
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
logging.debug("Saving config to {}".format(self._configfile))
with open(self._configfile, 'w') as f:
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.
"""
# FIXME to be implemented
2014-02-26 07:44:39 +01:00
pass
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
class ReadConfigParser(ConfigParser):
2014-02-07 20:21:50 +01:00
"""Our own ConfigParser subclass to read the main config.
2014-01-29 15:30:19 +01:00
2014-02-18 16:38:13 +01:00
Attributes:
_configdir: The directory to read the config from.
_configfile: The config file path.
2014-02-18 16:38:13 +01:00
"""
2014-01-27 21:42:00 +01:00
def __init__(self, configdir, fname):
2014-01-29 15:30:19 +01:00
"""Config constructor.
2014-02-19 10:58:32 +01:00
Args:
configdir: Directory to read the config from.
2014-02-19 10:58:32 +01:00
fname: Filename of the config file.
2014-02-07 20:21:50 +01:00
2014-01-29 15:30:19 +01:00
"""
super().__init__(interpolation=None)
2014-01-28 23:04:02 +01:00
self.optionxform = lambda opt: opt # be case-insensitive
2014-02-18 16:38:13 +01:00
self._configdir = configdir
self._configfile = os.path.join(self._configdir, fname)
if not os.path.isfile(self._configfile):
return
logging.debug("Reading config from {}".format(self._configfile))
self.read(self._configfile)
class ReadWriteConfigParser(ReadConfigParser):
2014-02-07 20:21:50 +01:00
"""ConfigParser subclass used for auxillary config files."""
2014-01-27 21:42:00 +01:00
def save(self):
2014-01-29 15:30:19 +01:00
"""Save the config file."""
2014-02-18 16:38:13 +01:00
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
logging.debug("Saving config to {}".format(self._configfile))
with open(self._configfile, 'w') as f:
2014-01-31 10:24:00 +01:00
self.write(f)
2014-03-27 22:37:34 +01:00
class SectionProxy(configparser.SectionProxy):
2014-04-07 17:20:14 +02:00
2014-03-27 22:37:34 +01:00
"""A proxy for a single section from a parser."""
2014-04-07 17:20:14 +02:00
# pylint: disable=redefined-builtin
2014-03-27 22:37:34 +01:00
def __getitem__(self, key):
return self._parser.get(self._name, key)
def __setitem__(self, key, value):
return self._parser.set(self._name, key, value)
def __delitem__(self, key):
# TODO
#if not (self._parser.has_option(self._name, key) and
# self._parser.remove_option(self._name, key)):
# raise KeyError(key)
raise NotImplementedError
def __contains__(self, key):
2014-04-09 17:57:00 +02:00
return self._parser.has_option(self._name, key)
2014-03-27 22:37:34 +01:00
def _options(self):
# TODO
2014-03-28 07:18:40 +01:00
return self._parser.config[self._name].values.keys()
2014-03-27 22:37:34 +01:00
def get(self, option, fallback=None, *, raw=False, vars=None):
2014-03-28 07:18:40 +01:00
return self._parser.get(self._name, option, raw=raw, fallback=fallback)
2014-03-27 22:37:34 +01:00
def getint(self, option, fallback=None, *, raw=False, vars=None):
raise NotImplementedError
def getfloat(self, option, fallback=None, *, raw=False, vars=None):
raise NotImplementedError
def getboolean(self, option, fallback=None, *, raw=False, vars=None):
raise NotImplementedError
@property
def parser(self):
# The parser object of the proxy is read-only.
return self._parser
@property
def name(self):
# The name of the section on a proxy is read-only.
return self._name