Initial parsing

This commit is contained in:
Florian Bruhin 2017-06-09 17:21:36 +02:00
parent f965805099
commit 52f6ea2525
7 changed files with 405 additions and 1409 deletions

View File

@ -73,10 +73,11 @@ class change_filter: # pylint: disable=invalid-name
optname: The option to be filtered. optname: The option to be filtered.
function: Whether a function rather than a method is decorated. function: Whether a function rather than a method is decorated.
""" """
if sectname not in configdata.DATA: # FIXME:conf
raise configexc.NoSectionError(sectname) # if sectname not in configdata.DATA:
if optname is not None and optname not in configdata.DATA[sectname]: # raise configexc.NoSectionError(sectname)
raise configexc.NoOptionError(optname, sectname) # if optname is not None and optname not in configdata.DATA[sectname]:
# raise configexc.NoOptionError(optname, sectname)
self._sectname = sectname self._sectname = sectname
self._optname = optname self._optname = optname
self._function = function self._function = function
@ -256,6 +257,7 @@ def init(parent=None):
parent: The parent to pass to QObjects which get initialized. parent: The parent to pass to QObjects which get initialized.
""" """
# _init_main_config(parent) # _init_main_config(parent)
configdata.init()
_init_new_config(parent) _init_new_config(parent)
_init_key_config(parent) _init_key_config(parent)
_init_misc() _init_misc()

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ ignore_case:
start_page: start_page:
type: type:
name: List name: List
elemtype: String valtype: String
default: ["https://start.duckduckgo.com"] default: ["https://start.duckduckgo.com"]
desc: The default page(s) to open at the start. desc: The default page(s) to open at the start.
yank_ignored_url_parameters: yank_ignored_url_parameters:
type: type:
name: List name: List
elemtype: String valtype: String
default: default:
- ref - ref
- utm_source - utm_source
@ -78,7 +78,7 @@ editor.command:
editor.encoding: editor.encoding:
type: Encoding type: Encoding
default: utf-8 default: utf-8
desc: Encoding to use for the editor. desc : Encoding to use for the editor.
content.private_browsing: content.private_browsing:
type: Bool type: Bool
@ -198,7 +198,7 @@ history_session_interval:
zoom.levels: zoom.levels:
type: type:
name: List name: List
elemtype: valtype:
name: Perc name: Perc
minval: 0 minval: 0
default: default:
@ -354,7 +354,7 @@ window.hide_wayland_decoration:
keyhint.blacklist: keyhint.blacklist:
type: type:
name: List name: List
elemtype: valtype:
name: String name: String
none_ok: true none_ok: true
default: "" default: ""
@ -417,11 +417,7 @@ content.user_agent:
content.proxy: content.proxy:
default: system default: system
type: type: Proxy
name: Proxy
valid_values:
- system: "Use the system wide proxy."
- none: "Don't use any proxy"
backend: backend:
QtWebKit: true QtWebKit: true
QtWebEngine: Qt 5.8 QtWebEngine: Qt 5.8
@ -1015,7 +1011,7 @@ content.host_blocking.lists:
- "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext" - "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext"
type: type:
name: List name: List
elemtype: Url valtype: Url
none_ok: true none_ok: true
desc: | desc: |
List of URLs of lists which contain hosts to block. List of URLs of lists which contain hosts to block.
@ -1037,7 +1033,7 @@ content.host_blocking.whitelist:
- piwik.org - piwik.org
type: type:
name: List name: List
valtype: string valtype: String
none_ok: true none_ok: true
desc: >- desc: >-
List of domains that should always be loaded, despite being ad-blocked. List of domains that should always be loaded, despite being ad-blocked.

View File

@ -35,7 +35,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar
from qutebrowser.commands import cmdutils from qutebrowser.commands import cmdutils
from qutebrowser.config import configexc from qutebrowser.config import configexc
from qutebrowser.utils import standarddir, utils from qutebrowser.utils import standarddir, utils, qtutils
SYSTEM_PROXY = object() # Return value for Proxy type SYSTEM_PROXY = object() # Return value for Proxy type
@ -313,27 +313,27 @@ class List(BaseType):
"""Base class for a (string-)list setting.""" """Base class for a (string-)list setting."""
_show_inner_type = True _show_valtype = True
def __init__(self, inner_type, none_ok=False, length=None): def __init__(self, valtype, none_ok=False, length=None):
super().__init__(none_ok) super().__init__(none_ok)
self.inner_type = inner_type self.valtype = valtype
self.length = length self.length = length
def get_name(self): def get_name(self):
name = super().get_name() name = super().get_name()
if self._show_inner_type: if self._show_valtype:
name += " of " + self.inner_type.get_name() name += " of " + self.valtype.get_name()
return name return name
def get_valid_values(self): def get_valid_values(self):
return self.inner_type.get_valid_values() return self.valtype.get_valid_values()
def transform(self, value): def transform(self, value):
if not value: if not value:
return None return None
else: else:
return [self.inner_type.transform(v.strip()) return [self.valtype.transform(v.strip())
for v in value.split(',')] for v in value.split(',')]
def validate(self, value): def validate(self, value):
@ -345,7 +345,7 @@ class List(BaseType):
raise configexc.ValidationError(value, "Exactly {} values need to " raise configexc.ValidationError(value, "Exactly {} values need to "
"be set!".format(self.length)) "be set!".format(self.length))
for val in vals: for val in vals:
self.inner_type.validate(val.strip()) self.valtype.validate(val.strip())
class FlagList(List): class FlagList(List):
@ -358,14 +358,14 @@ class FlagList(List):
combinable_values = None combinable_values = None
_show_inner_type = False _show_valtype = False
def __init__(self, none_ok=False, valid_values=None): def __init__(self, none_ok=False, valid_values=None):
super().__init__(BaseType(), none_ok) super().__init__(BaseType(), none_ok)
self.inner_type.valid_values = valid_values self.valtype.valid_values = valid_values
def validate(self, value): def validate(self, value):
if self.inner_type.valid_values is not None: if self.valtype.valid_values is not None:
super().validate(value) super().validate(value)
else: else:
self._basic_validation(value) self._basic_validation(value)
@ -379,7 +379,7 @@ class FlagList(List):
value, "List contains duplicate values!") value, "List contains duplicate values!")
def complete(self): def complete(self):
valid_values = self.inner_type.valid_values valid_values = self.valtype.valid_values
if valid_values is None: if valid_values is None:
return None return None
@ -453,11 +453,22 @@ class Int(BaseType):
def __init__(self, minval=None, maxval=None, none_ok=False): def __init__(self, minval=None, maxval=None, none_ok=False):
super().__init__(none_ok) super().__init__(none_ok)
if maxval is not None and minval is not None and maxval < minval: self.minval = self._parse_limit(minval)
raise ValueError("minval ({}) needs to be <= maxval ({})!".format( self.maxval = self._parse_limit(maxval)
minval, maxval)) if self.maxval is not None and self.minval is not None:
self.minval = minval if self.maxval < self.minval:
self.maxval = maxval raise ValueError("minval ({}) needs to be <= maxval ({})!"
.format(self.minval, self.maxval))
def _parse_limit(self, value):
if value == 'maxint':
return qtutils.MAXVALS['int']
elif value == 'maxint64':
return qtutils.MAXVALS['int64']
else:
if value is not None:
assert isinstance(value, int), value
return value
def transform(self, value): def transform(self, value):
if not value: if not value:
@ -1056,12 +1067,12 @@ class Padding(List):
"""Setting for paddings around elements.""" """Setting for paddings around elements."""
_show_inner_type = False _show_valtype = False
def __init__(self, none_ok=False, valid_values=None): def __init__(self, none_ok=False, valid_values=None):
super().__init__(Int(minval=0, none_ok=none_ok), super().__init__(Int(minval=0, none_ok=none_ok),
none_ok=none_ok, length=4) none_ok=none_ok, length=4)
self.inner_type.valid_values = valid_values self.valtype.valid_values = valid_values
def transform(self, value): def transform(self, value):
elems = super().transform(value) elems = super().transform(value)
@ -1179,10 +1190,17 @@ class Url(BaseType):
"{}".format(val.errorString())) "{}".format(val.errorString()))
class HeaderDict(BaseType): class Dict(BaseType):
"""A JSON-like dictionary for custom HTTP headers.""" """A JSON-like dictionary for custom HTTP headers."""
# FIXME:conf validate correctly
def __init__(self, keytype, valtype, none_ok=False):
super().__init__(none_ok)
self.keytype = keytype
self.valtype = valtype
def _validate_str(self, value, what): def _validate_str(self, value, what):
"""Check if the given thing is an ascii-only string. """Check if the given thing is an ascii-only string.
@ -1274,8 +1292,8 @@ class ConfirmQuit(FlagList):
def __init__(self, none_ok=False): def __init__(self, none_ok=False):
super().__init__(none_ok) super().__init__(none_ok)
self.inner_type.none_ok = none_ok self.valtype.none_ok = none_ok
self.inner_type.valid_values = ValidValues( self.valtype.valid_values = ValidValues(
('always', "Always show a confirmation."), ('always', "Always show a confirmation."),
('multiple-tabs', "Show a confirmation if " ('multiple-tabs', "Show a confirmation if "
"multiple tabs are opened."), "multiple tabs are opened."),

View File

@ -48,14 +48,16 @@ class NewConfigManager(QObject):
super().__init__(parent) super().__init__(parent)
self._values = {} self._values = {}
def _key(self, sect, opt): def _key(self, sect, opt=None):
return sect + ' -> ' + opt if opt is None:
# New usage
return sect
return sect + '.' + opt
def read_defaults(self): def read_defaults(self):
for name, section in configdata.data().items(): for name, option in configdata.DATA.items():
for key, value in section.items(): self._values[name] = option
self._values[self._key(name, key)] = value
def get(self, section, option): def get(self, section, option):
val = self._values[self._key(section, option)] val = self._values[self._key(section, option)]
return val.typ.transform(val.value()) return val.typ.transform(val.default)

View File

@ -23,16 +23,11 @@ import sys
import os import os
import os.path import os.path
import yaml
import astroid import astroid
from pylint import interfaces, checkers from pylint import interfaces, checkers
from pylint.checkers import utils from pylint.checkers import utils
sys.path.insert(
0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
os.pardir))
from qutebrowser.config import configdata
class ConfigChecker(checkers.BaseChecker): class ConfigChecker(checkers.BaseChecker):

View File

@ -18,27 +18,198 @@
"""Tests for qutebrowser.config.configdata.""" """Tests for qutebrowser.config.configdata."""
import textwrap
import yaml
import pytest import pytest
from qutebrowser.config import configdata from qutebrowser.config import configdata, configtypes
from qutebrowser.utils import usertypes
@pytest.mark.parametrize('sect', configdata.DATA.keys()) def test_init():
def test_section_desc(sect): """Test reading the default yaml file."""
"""Make sure every section has a description.""" configdata.init()
desc = configdata.SECTION_DESC[sect] assert isinstance(configdata.DATA, dict)
assert isinstance(desc, str) assert 'ignore_case' in configdata.DATA
def test_data(): class TestReadYaml:
"""Some simple sanity tests on data()."""
data = configdata.data()
assert 'general' in data
assert 'ignore-case' in data['general']
def test_readonly_data(): def test_valid(self):
"""Make sure DATA is readonly.""" data = textwrap.dedent("""
with pytest.raises(ValueError, match="Trying to modify a read-only " test1:
"config!"): type: Bool
configdata.DATA['general'].setv('temp', 'ignore-case', 'true', 'true') default: true
desc: Hello World
test2:
type: String
default: foo
backend: QtWebKit
desc: Hello World 2
""")
data = configdata._read_yaml(data)
assert data.keys() == {'test1', 'test2'}
assert data['test1'].description == "Hello World"
assert data['test2'].default == "foo"
assert data['test2'].backends == [usertypes.Backend.QtWebKit]
assert isinstance(data['test1'].typ, configtypes.Bool)
def test_invalid_keys(self):
"""Test reading with unknown keys."""
data = textwrap.dedent("""
test:
type: Bool
default: true
desc: Hello World
hello: world
""",)
with pytest.raises(ValueError, match='Invalid keys'):
configdata._read_yaml(data)
class TestParseYamlType:
def _yaml(self, s):
"""Get the type from parsed YAML data."""
return yaml.load(textwrap.dedent(s))['type']
def test_simple(self):
"""Test type which is only a name."""
data = self._yaml("type: Bool")
typ = configdata._parse_yaml_type('test', data)
assert isinstance(typ, configtypes.Bool)
assert not typ.none_ok
def test_complex(self):
"""Test type parsing with arguments."""
data = self._yaml("""
type:
name: String
minlen: 2
""")
typ = configdata._parse_yaml_type('test', data)
assert isinstance(typ, configtypes.String)
assert not typ.none_ok
assert typ.minlen == 2
def test_list(self):
"""Test type parsing with a list and subtypes."""
data = self._yaml("""
type:
name: List
valtype: String
""")
typ = configdata._parse_yaml_type('test', data)
assert isinstance(typ, configtypes.List)
assert isinstance(typ.valtype, configtypes.String)
assert not typ.none_ok
assert not typ.valtype.none_ok
def test_dict(self):
"""Test type parsing with a dict and subtypes."""
data = self._yaml("""
type:
name: Dict
keytype: String
valtype:
name: Int
minval: 10
""")
typ = configdata._parse_yaml_type('test', data)
assert isinstance(typ, configtypes.Dict)
assert isinstance(typ.keytype, configtypes.String)
assert isinstance(typ.valtype, configtypes.Int)
assert not typ.none_ok
assert typ.valtype.minval == 10
def test_invalid_node(self):
"""Test type parsing with invalid node type."""
data = self._yaml("type: 42")
with pytest.raises(ValueError, match="Invalid node for test while "
"reading type: 42"):
configdata._parse_yaml_type('test', data)
def test_unknown_type(self):
"""Test type parsing with type which doesn't exist."""
data = self._yaml("type: Foobar")
with pytest.raises(AttributeError,
match="Did not find type Foobar for test"):
configdata._parse_yaml_type('test', data)
def test_unknown_dict(self):
"""Test type parsing with a dict without keytype."""
data = self._yaml("type: Dict")
with pytest.raises(ValueError, match="Invalid node for test while "
"reading 'keytype': 'Dict'"):
configdata._parse_yaml_type('test', data)
def test_unknown_args(self):
"""Test type parsing with unknown type arguments."""
data = self._yaml("""
type:
name: Int
answer: 42
""")
with pytest.raises(TypeError, match="Error while creating Int"):
configdata._parse_yaml_type('test', data)
class TestParseYamlBackend:
def _yaml(self, s):
"""Get the type from parsed YAML data."""
return yaml.load(textwrap.dedent(s))['backend']
@pytest.mark.parametrize('backend, expected', [
('QtWebKit', [usertypes.Backend.QtWebKit]),
('QtWebEngine', [usertypes.Backend.QtWebEngine]),
# This is also what _parse_yaml_backends gets when backend: is not given
# at all
('null', [usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine]),
])
def test_simple(self, backend, expected):
"""Check a simple "backend: QtWebKit"."""
data = self._yaml("backend: {}".format(backend))
backends = configdata._parse_yaml_backends('test', data)
assert backends == expected
@pytest.mark.parametrize('webkit, has_new_version, expected', [
(True, True, [usertypes.Backend.QtWebKit,
usertypes.Backend.QtWebEngine]),
(False, True, [usertypes.Backend.QtWebEngine]),
(True, False, [usertypes.Backend.QtWebKit]),
])
def test_dict(self, monkeypatch, webkit, has_new_version, expected):
data = self._yaml("""
backend:
QtWebKit: {}
QtWebEngine: Qt 5.8
""".format('true' if webkit else 'false'))
monkeypatch.setattr(configdata.qtutils, 'version_check',
lambda v: has_new_version)
backends = configdata._parse_yaml_backends('test', data)
assert backends == expected
@pytest.mark.parametrize('yaml_data', [
# Wrong type
"backend: 42",
# Unknown key
"""
backend:
QtWebKit: true
QtWebEngine: true
foo: bar
""",
# Missing key
"""
backend:
QtWebKit: true
""",
])
def test_invalid_backend(self, yaml_data):
with pytest.raises(ValueError, match="Invalid node for test while "
"reading backends:"):
configdata._parse_yaml_backends('test', self._yaml(yaml_data))