Initial config.py support

See #2795
This commit is contained in:
Florian Bruhin 2017-09-14 16:16:14 +02:00
parent ed6933a839
commit cb806aefa3
5 changed files with 168 additions and 13 deletions

View File

@ -30,6 +30,7 @@ disable=no-self-use,
broad-except,
bare-except,
eval-used,
exec-used,
ungrouped-imports,
suppressed-message,
too-many-return-statements,

View File

@ -625,13 +625,16 @@ def init(parent=None):
val = ConfigContainer(instance)
key_instance = KeyConfig(instance)
for cf in _change_filters:
cf.validate()
configtypes.Font.monospace_fonts = val.fonts.monospace
config_commands = ConfigCommands(instance, key_instance)
objreg.register('config-commands', config_commands)
for cf in _change_filters:
cf.validate()
instance.read_yaml()
config_api = configfiles.read_config_py()
if getattr(config_api, 'load_autoconfig', True):
instance.read_yaml()
configfiles.init(instance)

View File

@ -19,6 +19,7 @@
"""Configuration files residing on disk."""
import types
import os.path
import textwrap
import configparser
@ -90,6 +91,63 @@ class YamlConfig:
pass
class ConfigAPI:
"""Object which gets passed to config.py as "config" object.
This is a small wrapper over the Config object, but with more
straightforward method names (get/set call get_obj/set_obj) and a more
shallow API.
Attributes:
_config: The main Config object to use.
_keyconfig: The KeyConfig object.
val: A matching ConfigContainer object.
load_autoconfig: Whether autoconfig.yml should be loaded.
"""
def __init__(self, config, keyconfig, container):
self._config = config
self._keyconfig = keyconfig
self.val = container
self.load_autoconfig = True
def get(self, name):
return self._config.get_obj(name)
def set(self, name, value):
self._config.set_obj(name, value)
def bind(self, key, command, *, mode, force=False):
self._keyconfig.bind(key, command, mode=mode, force=force)
def unbind(self, key, *, mode):
self._keyconfig.unbind(key, mode=mode)
def read_config_py(filename=None):
"""Read a config.py file."""
from qutebrowser.config import config
# FIXME:conf error handling
if filename is None:
filename = os.path.join(standarddir.config(), 'config.py')
if not os.path.exists(filename):
return None
api = ConfigAPI(config.instance, config.key_instance, config.val)
module = types.ModuleType('config')
module.config = api
module.c = api.val
module.__file__ = filename
with open(filename, mode='rb') as f:
source = f.read()
code = compile(source, filename, 'exec')
exec(code, module.__dict__)
return api
def init(config):
"""Initialize config storage not related to the main config."""
state = StateConfig()

View File

@ -852,16 +852,24 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
monkeypatch.setattr(config, 'key_instance', None)
monkeypatch.setattr(config, '_change_filters', [])
yield
objreg.delete('config-commands')
try:
objreg.delete('state-config')
except KeyError:
pass
for obj in ['config-commands', 'state-config', 'command-history']:
try:
objreg.delete(obj)
except KeyError:
pass
def test_init(init_patch, fake_save_manager, config_tmpdir):
(config_tmpdir / 'autoconfig.yml').write_text(
'global:\n colors.hints.fg: magenta', 'utf-8', ensure=True)
@pytest.mark.parametrize('load_autoconfig', [True, False])
def test_init(init_patch, fake_save_manager, config_tmpdir, load_autoconfig):
autoconfig_file = config_tmpdir / 'autoconfig.yml'
config_py_file = config_tmpdir / 'config.py'
autoconfig_file.write_text('global:\n colors.hints.fg: magenta\n',
'utf-8', ensure=True)
config_py_lines = ['c.colors.hints.bg = "red"']
if not load_autoconfig:
config_py_lines.append('config.load_autoconfig = False')
config_py_file.write_text('\n'.join(config_py_lines), 'utf-8', ensure=True)
config.init()
@ -875,7 +883,11 @@ def test_init(init_patch, fake_save_manager, config_tmpdir):
fake_save_manager.add_saveable.assert_any_call(
'yaml-config', unittest.mock.ANY)
assert config.instance._values['colors.hints.fg'] == 'magenta'
assert config.instance._values['colors.hints.bg'] == 'red'
if load_autoconfig:
assert config.instance._values['colors.hints.fg'] == 'magenta'
else:
assert 'colors.hints.fg' not in config.instance._values
def test_init_invalid_change_filter(init_patch):

View File

@ -22,7 +22,7 @@ import sys
import pytest
from qutebrowser.config import configfiles
from qutebrowser.config import config, configfiles
from qutebrowser.utils import objreg
from PyQt5.QtCore import QSettings
@ -91,6 +91,87 @@ def test_yaml_config(fake_save_manager, config_tmpdir, old_config, insert):
assert ' tabs.show: never' in lines
class TestConfigPy:
"""Tests for ConfigAPI and read_config_py()."""
pytestmark = pytest.mark.usefixtures('config_stub', 'key_config_stub')
class ConfPy:
"""Helper class to get a confpy fixture."""
def __init__(self, tmpdir):
self._confpy = tmpdir / 'config.py'
self.filename = str(self._confpy)
def write(self, *lines):
text = '\n'.join(lines)
self._confpy.write_text(text, 'utf-8', ensure=True)
@pytest.fixture
def confpy(self, tmpdir):
return self.ConfPy(tmpdir)
@pytest.mark.parametrize('line', [
'c.colors.hints.bg = "red"',
'config.val.colors.hints.bg = "red"',
'config.set("colors.hints.bg", "red")',
])
def test_set(self, confpy, line):
confpy.write(line)
configfiles.read_config_py(confpy.filename)
assert config.instance._values['colors.hints.bg'] == 'red'
@pytest.mark.parametrize('set_first', [True, False])
@pytest.mark.parametrize('get_line', [
'c.colors.hints.fg',
'config.get("colors.hints.fg")',
])
def test_get(self, confpy, set_first, get_line):
"""Test whether getting options works correctly.
We test this by doing the following:
- Set colors.hints.fg to some value (inside the config.py with
set_first, outside of it otherwise).
- In the config.py, read .fg and set .bg to the same value.
- Verify that .bg has been set correctly.
"""
# pylint: disable=bad-config-option
config.val.colors.hints.fg = 'green'
if set_first:
confpy.write('c.colors.hints.fg = "red"',
'c.colors.hints.bg = {}'.format(get_line))
expected = 'red'
else:
confpy.write('c.colors.hints.bg = {}'.format(get_line))
expected = 'green'
configfiles.read_config_py(confpy.filename)
assert config.instance._values['colors.hints.bg'] == expected
def test_bind(self, confpy):
confpy.write('config.bind(",a", "message-info foo", mode="normal")')
configfiles.read_config_py(confpy.filename)
expected = {'normal': {',a': 'message-info foo'}}
assert config.instance._values['bindings.commands'] == expected
def test_unbind(self, confpy):
confpy.write('config.unbind("o", mode="normal")')
configfiles.read_config_py(confpy.filename)
expected = {'normal': {'o': None}}
assert config.instance._values['bindings.commands'] == expected
def test_reading_default_location(self, config_tmpdir):
(config_tmpdir / 'config.py').write_text(
'c.colors.hints.bg = "red"', 'utf-8')
configfiles.read_config_py()
assert config.instance._values['colors.hints.bg'] == 'red'
def test_reading_missing_default_location(self, config_tmpdir):
assert not (config_tmpdir / 'config.py').exists()
configfiles.read_config_py() # Should not crash
@pytest.fixture
def init_patch(qapp, fake_save_manager, config_tmpdir, data_tmpdir,
config_stub):