246 lines
7.2 KiB
Python
246 lines
7.2 KiB
Python
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
#
|
|
# This file is part of qutebrowser.
|
|
#
|
|
# qutebrowser is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# qutebrowser is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""Configuration data for config.py.
|
|
|
|
Module attributes:
|
|
|
|
DATA: A dict of Option objects after init() has been called.
|
|
"""
|
|
|
|
import functools
|
|
|
|
import attr
|
|
from qutebrowser.config import configtypes
|
|
from qutebrowser.utils import usertypes, qtutils, utils
|
|
|
|
DATA = None
|
|
MIGRATIONS = None
|
|
|
|
|
|
@attr.s
|
|
class Option:
|
|
|
|
"""Description of an Option in the config.
|
|
|
|
Note that this is just an option which exists, with no value associated.
|
|
"""
|
|
|
|
name = attr.ib()
|
|
typ = attr.ib()
|
|
default = attr.ib()
|
|
backends = attr.ib()
|
|
raw_backends = attr.ib()
|
|
description = attr.ib()
|
|
restart = attr.ib(default=False)
|
|
|
|
|
|
@attr.s
|
|
class Migrations:
|
|
|
|
"""Nigrated options in configdata.yml."""
|
|
|
|
renamed = attr.ib(default=attr.Factory(dict))
|
|
deleted = attr.ib(default=attr.Factory(list))
|
|
|
|
|
|
def _raise_invalid_node(name, what, node):
|
|
"""Raise an exception for an invalid configdata YAML node.
|
|
|
|
Args:
|
|
name: The name of the setting being parsed.
|
|
what: The name of the thing being parsed.
|
|
node: The invalid node.
|
|
"""
|
|
raise ValueError("Invalid node for {} while reading {}: {!r}".format(
|
|
name, what, node))
|
|
|
|
|
|
def _parse_yaml_type(name, node):
|
|
if isinstance(node, str):
|
|
# e.g:
|
|
# type: Bool
|
|
# -> create the type object without any arguments
|
|
type_name = node
|
|
kwargs = {}
|
|
elif isinstance(node, dict):
|
|
# e.g:
|
|
# type:
|
|
# name: String
|
|
# none_ok: true
|
|
# -> create the type object and pass arguments
|
|
type_name = node.pop('name')
|
|
kwargs = node
|
|
valid_values = kwargs.get('valid_values', None)
|
|
if valid_values is not None:
|
|
kwargs['valid_values'] = configtypes.ValidValues(*valid_values)
|
|
else:
|
|
_raise_invalid_node(name, 'type', node)
|
|
|
|
try:
|
|
typ = getattr(configtypes, type_name)
|
|
except AttributeError as e:
|
|
raise AttributeError("Did not find type {} for {}".format(
|
|
type_name, name))
|
|
|
|
# Parse sub-types
|
|
try:
|
|
if typ is configtypes.Dict:
|
|
kwargs['keytype'] = _parse_yaml_type(name, kwargs['keytype'])
|
|
kwargs['valtype'] = _parse_yaml_type(name, kwargs['valtype'])
|
|
elif typ is configtypes.List or typ is configtypes.ListOrValue:
|
|
kwargs['valtype'] = _parse_yaml_type(name, kwargs['valtype'])
|
|
except KeyError as e:
|
|
_raise_invalid_node(name, str(e), node)
|
|
|
|
try:
|
|
return typ(**kwargs)
|
|
except TypeError as e:
|
|
raise TypeError("Error while creating {} with {}: {}".format(
|
|
type_name, node, e))
|
|
|
|
|
|
def _parse_yaml_backends_dict(name, node):
|
|
"""Parse a dict definition for backends.
|
|
|
|
Example:
|
|
|
|
backends:
|
|
QtWebKit: true
|
|
QtWebEngine: Qt 5.9
|
|
"""
|
|
str_to_backend = {
|
|
'QtWebKit': usertypes.Backend.QtWebKit,
|
|
'QtWebEngine': usertypes.Backend.QtWebEngine,
|
|
}
|
|
|
|
if node.keys() != str_to_backend.keys():
|
|
_raise_invalid_node(name, 'backends', node)
|
|
|
|
backends = []
|
|
|
|
# The value associated to the key, and whether we should add that backend
|
|
# or not.
|
|
conditionals = {
|
|
True: True,
|
|
False: False,
|
|
'Qt 5.8': qtutils.version_check('5.8'),
|
|
'Qt 5.9': qtutils.version_check('5.9'),
|
|
}
|
|
for key in sorted(node.keys()):
|
|
if conditionals[node[key]]:
|
|
backends.append(str_to_backend[key])
|
|
|
|
return backends
|
|
|
|
|
|
def _parse_yaml_backends(name, node):
|
|
"""Parse a backend node in the yaml.
|
|
|
|
It can have one of those four forms:
|
|
- Not present -> setting applies to both backends.
|
|
- backend: QtWebKit -> setting only available with QtWebKit
|
|
- backend: QtWebEngine -> setting only available with QtWebEngine
|
|
- backend:
|
|
QtWebKit: true
|
|
QtWebEngine: Qt 5.9
|
|
-> setting available based on the given conditionals.
|
|
|
|
Return:
|
|
A list of backends.
|
|
"""
|
|
if node is None:
|
|
return [usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine]
|
|
elif node == 'QtWebKit':
|
|
return [usertypes.Backend.QtWebKit]
|
|
elif node == 'QtWebEngine':
|
|
return [usertypes.Backend.QtWebEngine]
|
|
elif isinstance(node, dict):
|
|
return _parse_yaml_backends_dict(name, node)
|
|
_raise_invalid_node(name, 'backends', node)
|
|
|
|
|
|
def _read_yaml(yaml_data):
|
|
"""Read config data from a YAML file.
|
|
|
|
Args:
|
|
yaml_data: The YAML string to parse.
|
|
|
|
Return:
|
|
A tuple with two elements:
|
|
- A dict mapping option names to Option elements.
|
|
- A Migrations object.
|
|
"""
|
|
parsed = {}
|
|
migrations = Migrations()
|
|
data = utils.yaml_load(yaml_data)
|
|
|
|
keys = {'type', 'default', 'desc', 'backend', 'restart'}
|
|
|
|
for name, option in data.items():
|
|
if set(option.keys()) == {'renamed'}:
|
|
migrations.renamed[name] = option['renamed']
|
|
continue
|
|
if set(option.keys()) == {'deleted'}:
|
|
value = option['deleted']
|
|
if value is not True:
|
|
raise ValueError("Invalid deleted value: {}".format(value))
|
|
migrations.deleted.append(name)
|
|
continue
|
|
|
|
if not set(option.keys()).issubset(keys):
|
|
raise ValueError("Invalid keys {} for {}".format(
|
|
option.keys(), name))
|
|
|
|
backends = option.get('backend', None)
|
|
|
|
parsed[name] = Option(
|
|
name=name,
|
|
typ=_parse_yaml_type(name, option['type']),
|
|
default=option['default'],
|
|
backends=_parse_yaml_backends(name, backends),
|
|
raw_backends=backends if isinstance(backends, dict) else None,
|
|
description=option['desc'],
|
|
restart=option.get('restart', False))
|
|
|
|
# Make sure no key shadows another.
|
|
for key1 in parsed:
|
|
for key2 in parsed:
|
|
if key2.startswith(key1 + '.'):
|
|
raise ValueError("Shadowing keys {} and {}".format(key1, key2))
|
|
|
|
# Make sure rename targets actually exist.
|
|
for old, new in migrations.renamed.items():
|
|
if new not in parsed:
|
|
raise ValueError("Renaming {} to unknown {}".format(old, new))
|
|
|
|
return parsed, migrations
|
|
|
|
|
|
@functools.lru_cache(maxsize=256)
|
|
def is_valid_prefix(prefix):
|
|
"""Check whether the given prefix is a valid prefix for some option."""
|
|
return any(key.startswith(prefix + '.') for key in DATA)
|
|
|
|
|
|
def init():
|
|
"""Initialize configdata from the YAML file."""
|
|
global DATA, MIGRATIONS
|
|
DATA, MIGRATIONS = _read_yaml(utils.read_file('config/configdata.yml'))
|