diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py
index 555a7a22b..26dff2b3c 100644
--- a/qutebrowser/config/config.py
+++ b/qutebrowser/config/config.py
@@ -17,135 +17,366 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see .
-"""Configuration storage and config-related utilities.
+"""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 inherits from configparser,
-but we borrow some methods and classes from there where it makes sense.
-"""
-
-import re
-import os
-import sys
import os.path
+import contextlib
import functools
import configparser
-import contextlib
-import collections
-import collections.abc
from PyQt5.QtCore import pyqtSignal, QObject, QUrl, QSettings
-from PyQt5.QtGui import QColor
-from qutebrowser.config import configdata, configexc, textwrapper, newconfig
-from qutebrowser.config.parsers import keyconf
-from qutebrowser.config.parsers import ini
+from qutebrowser.config import configdata, configexc, configtypes
+from qutebrowser.utils import utils, objreg, message, standarddir
from qutebrowser.commands import cmdexc, cmdutils
-from qutebrowser.utils import (message, objreg, utils, standarddir, log,
- qtutils, error, usertypes)
-from qutebrowser.misc import objects
-from qutebrowser.utils.usertypes import Completion
-
-# FIXME:conf compat
-from qutebrowser.config.newconfig import change_filter
-UNSET = object()
-
-
-# FIXME:conf for new config
+# An easy way to access the config from other code via config.val.foo
val = None
instance = None
key_instance = None
+# Keeping track of all change filters to validate them later.
+_change_filters = []
-def _init_main_config(parent=None):
- """Initialize the main config.
+
+class change_filter: # pylint: disable=invalid-name
+
+ """Decorator to filter calls based on a config section/option matching.
+
+ This could also be a function, but as a class (with a "wrong" name) it's
+ much cleaner to implement.
+
+ Attributes:
+ _option: An option or prefix to be filtered
+ _function: Whether a function rather than a method is decorated.
+ """
+
+ def __init__(self, option, function=False):
+ """Save decorator arguments.
+
+ Gets called on parse-time with the decorator arguments.
+
+ Args:
+ option: The option to be filtered.
+ function: Whether a function rather than a method is decorated.
+ """
+ self._option = option
+ self._function = function
+ _change_filters.append(self)
+
+ def validate(self):
+ """Make sure the configured option or prefix exists.
+
+ We can't do this in __init__ as configdata isn't ready yet.
+ """
+ if (self._option not in configdata.DATA and
+ not configdata.is_valid_prefix(self._option)):
+ raise configexc.NoOptionError(self._option)
+
+ def _check_match(self, option):
+ """Check if the given option matches the filter."""
+ if option is None:
+ # Called directly, not from a config change event.
+ return True
+ elif option == self._option:
+ return True
+ elif option.startswith(self._option + '.'):
+ # prefix match
+ return True
+ else:
+ return False
+
+ def __call__(self, func):
+ """Filter calls to the decorated function.
+
+ Gets called when a function should be decorated.
+
+ Adds a filter which returns if we're not interested in the change-event
+ and calls the wrapped function if we are.
+
+ We assume the function passed doesn't take any parameters.
+
+ Args:
+ func: The function to be decorated.
+
+ Return:
+ The decorated function.
+ """
+ if self._function:
+ @functools.wraps(func)
+ def wrapper(option=None):
+ if self._check_match(option):
+ return func()
+ else:
+ @functools.wraps(func)
+ def wrapper(wrapper_self, option=None):
+ if self._check_match(option):
+ return func(wrapper_self)
+
+ return wrapper
+
+
+class NewKeyConfig:
+
+ def get_reverse_bindings_for(self, section):
+ """Get a dict of commands to a list of bindings for the section."""
+ cmd_to_keys = {}
+ bindings = val.bindings.commands[section]
+ if bindings is None:
+ return cmd_to_keys
+ for key, full_cmd in bindings.items():
+ for cmd in full_cmd.split(';;'):
+ cmd = cmd.strip()
+ cmd_to_keys.setdefault(cmd, [])
+ # put special bindings last
+ if utils.is_special_key(key):
+ cmd_to_keys[cmd].append(key)
+ else:
+ cmd_to_keys[cmd].insert(0, key)
+ return cmd_to_keys
+
+
+class ConfigCommands:
+
+ def __init__(self, config):
+ self._config = config
+
+ @cmdutils.register(instance='config-commands', star_args_optional=True)
+ @cmdutils.argument('win_id', win_id=True)
+ def set(self, win_id, option=None, *values, temp=False, print_=False):
+ """Set an option.
+
+ If the option name ends with '?', the value of the option is shown
+ instead.
+
+ If the option name ends with '!' and it is a boolean value, toggle it.
+
+ //
+
+ Args:
+ option: The name of the option.
+ values: The value to set, or the values to cycle through.
+ temp: Set value temporarily.
+ print_: Print the value after setting.
+ """
+ # FIXME:conf write to YAML if temp isn't used!
+ if option is None:
+ tabbed_browser = objreg.get('tabbed-browser', scope='window',
+ window=win_id)
+ tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
+ return
+
+ if option.endswith('?') and option != '?':
+ self._print_value(option[:-1])
+ return
+
+ with self._handle_config_error():
+ if option.endswith('!') and option != '!' and not values:
+ # Handle inversion as special cases of the cycle code path
+ option = option[:-1]
+ opt = self._config.get_opt(option)
+ if opt.typ is configtypes.Bool:
+ values = ['false', 'true']
+ else:
+ raise cmdexc.CommandError(
+ "set: Attempted inversion of non-boolean value.")
+ elif not values:
+ raise cmdexc.CommandError("set: The following arguments "
+ "are required: value")
+ self._set_next(option, values)
+
+ if print_:
+ self._print_value(option)
+
+ def _print_value(self, option):
+ """Print the value of the given option."""
+ with self._handle_config_error():
+ val = self._config.get_str(option)
+ message.info("{} = {}".format(option, val))
+
+ def _set_next(self, option, values):
+ """Set the next value out of a list of values."""
+ if len(values) == 1:
+ # If we have only one value, just set it directly (avoid
+ # breaking stuff like aliases or other pseudo-settings)
+ self._config.set(option, values[0])
+ return
+
+ # Use the next valid value from values, or the first if the current
+ # value does not appear in the list
+ val = self._config.get_str(option)
+ try:
+ idx = values.index(str(val))
+ idx = (idx + 1) % len(values)
+ value = values[idx]
+ except ValueError:
+ value = values[0]
+ self._config.set(option, value)
+
+ @contextlib.contextmanager
+ def _handle_config_error(self):
+ """Catch errors in set_command and raise CommandError."""
+ try:
+ yield
+ except (configexc.NoOptionError, configexc.ValidationError) as e:
+ raise cmdexc.CommandError("set: {}".format(e))
+ except configexc.Error as e:
+ raise cmdexc.CommandError("set: {} - {}".format(
+ e.__class__.__name__, e))
+
+
+class NewConfigManager(QObject):
+
+ changed = pyqtSignal(str) # FIXME:conf stub...
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.options = {}
+ self._values = {} # FIXME:conf stub
+
+ def read_defaults(self):
+ for name, option in configdata.DATA.items():
+ self.options[name] = option
+
+ def get_opt(self, name):
+ try:
+ return self.options[name]
+ except KeyError:
+ raise configexc.NoOptionError(name)
+
+ def get(self, name):
+ opt = self.get_opt(name)
+ value = self._values.get(name, opt.default)
+ return opt.typ.to_py(value)
+
+ def get_str(self, name):
+ opt = self.get_opt(name)
+ return opt.typ.to_str(opt.default)
+
+ def set(self, name, value):
+ # FIXME:conf stub
+ try:
+ opt = self.options[name]
+ except KeyError:
+ raise configexc.NoOptionError(name)
+ self._values[name] = opt.typ.from_str(value)
+ self.changed.emit(name)
+
+ def dump_userconfig(self):
+ """Get the part of the config which was changed by the user.
+
+ Return:
+ The changed config part as string.
+ """
+ lines = ['{} = {}'.format(optname, value)
+ for optname, value in self._values.items()]
+ if not lines:
+ lines = ['']
+ return '\n'.join(lines)
+
+
+class ConfigContainer:
+
+ """An object implementing config access via __getattr__.
+
+ Attributes:
+ _manager: The ConfigManager object.
+ _prefix: The __getattr__ chain leading up to this object.
+ """
+
+ def __init__(self, manager, prefix=''):
+ self._manager = manager
+ self._prefix = prefix
+
+ def __repr__(self):
+ return utils.get_repr(self, constructor=True, manager=self._manager,
+ prefix=self._prefix)
+
+ def __getattr__(self, attr):
+ """Get an option or a new ConfigContainer with the added prefix.
+
+ If we get an option which exists, we return the value for it.
+ If we get a part of an option name, we return a new ConfigContainer.
+
+ Those two never overlap as configdata.py ensures there are no shadowing
+ options.
+ """
+ name = self._join(attr)
+ if configdata.is_valid_prefix(name):
+ return ConfigContainer(manager=self._manager, prefix=name)
+ try:
+ return self._manager.get(name)
+ except configexc.NoOptionError as e:
+ # If it's not a valid prefix - re-raise to improve error text.
+ raise configexc.NoOptionError(name)
+
+ def __setattr__(self, attr, value):
+ if attr.startswith('_'):
+ return super().__setattr__(attr, value)
+ self._handler(self._join(attr), value)
+
+ def _join(self, attr):
+ if self._prefix:
+ return '{}.{}'.format(self._prefix, attr)
+ else:
+ return attr
+
+
+class StateConfig(configparser.ConfigParser):
+
+ """The "state" file saving various application state."""
+
+ def __init__(self):
+ super().__init__()
+ save_manager = objreg.get('save-manager')
+ self._filename = os.path.join(standarddir.data(), 'state')
+ self.read(self._filename, encoding='utf-8')
+ for sect in ['general', 'geometry']:
+ try:
+ self.add_section(sect)
+ except configparser.DuplicateSectionError:
+ pass
+ # See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
+ self['general'].pop('fooled', None)
+ save_manager.add_saveable('state-config', self._save)
+
+ def _save(self):
+ """Save the state file to the configured location."""
+ with open(self._filename, 'w', encoding='utf-8') as f:
+ self.write(f)
+
+
+def init(parent=None):
+ """Initialize the config.
Args:
- parent: The parent to pass to ConfigManager.
+ parent: The parent to pass to QObjects which get initialized.
"""
- args = objreg.get('args')
- config_obj = ConfigManager(parent=parent)
- try:
- config_obj.read(standarddir.config(), 'qutebrowser.conf',
- relaxed=args.relaxed_config)
- except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
- log.init.exception(e)
- errstr = "Error while reading config:"
- try:
- errstr += "\n\n{} -> {}:".format(
- e.section, e.option)
- except AttributeError:
- pass
- errstr += "\n"
- error.handle_fatal_exc(e, args, "Error while reading config!",
- pre_text=errstr)
- # We didn't really initialize much so far, so we just quit hard.
- sys.exit(usertypes.Exit.err_config)
- else:
- objreg.register('config', config_obj)
+ configdata.init()
- save_manager = objreg.get('save-manager')
- save_manager.add_saveable(
- 'config', config_obj.save, config_obj.changed,
- config_opt='auto_save.config', filename=filename)
- for sect in config_obj.sections.values():
- for opt in sect.values.values():
- if opt.values['conf'] is None:
- # Option added to built-in defaults but not in user's
- # config yet
- save_manager.save('config', explicit=True, force=True)
- return
+ new_config = NewConfigManager(parent)
+ new_config.read_defaults()
+ objreg.register('config', new_config)
+ config_commands = ConfigCommands(new_config)
+ objreg.register('config-commands', config_commands)
-def _init_key_config(parent):
- """Initialize the key config.
+ global val, instance, key_instance
+ val = ConfigContainer(new_config)
+ instance = new_config
+ key_instance = NewKeyConfig()
- Args:
- parent: The parent to use for the KeyConfigParser.
- """
- args = objreg.get('args')
- try:
- key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf',
- args.relaxed_config,
- parent=parent)
- except (keyconf.KeyConfigError, cmdexc.CommandError,
- UnicodeDecodeError) as e:
- log.init.exception(e)
- errstr = "Error while reading key config:\n"
- if e.lineno is not None:
- errstr += "In line {}: ".format(e.lineno)
- error.handle_fatal_exc(e, args, "Error while reading key config!",
- pre_text=errstr)
- # We didn't really initialize much so far, so we just quit hard.
- sys.exit(usertypes.Exit.err_key_config)
- else:
- objreg.register('key-config', key_config)
- save_manager = objreg.get('save-manager')
- filename = os.path.join(standarddir.config(), 'keys.conf')
- save_manager.add_saveable(
- 'key-config', key_config.save, key_config.config_dirty,
- config_opt='auto_save.config', filename=filename,
- dirty=key_config.is_dirty)
+ for cf in _change_filters:
+ cf.validate()
-
-def _init_misc():
- """Initialize misc. config-related files."""
- save_manager = objreg.get('save-manager')
- state_config = ini.ReadWriteConfigParser(standarddir.data(), 'state')
- for sect in ['general', 'geometry']:
- try:
- state_config.add_section(sect)
- except configparser.DuplicateSectionError:
- pass
- # See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
- state_config['general'].pop('fooled', None)
- objreg.register('state-config', state_config)
- save_manager.add_saveable('state-config', state_config.save)
+ state = StateConfig()
+ objreg.register('state-config', state)
# We need to import this here because lineparser needs config.
+ # FIXME:conf add this to the Command widget or something?
from qutebrowser.misc import lineparser
+ save_manager = objreg.get('save-manager')
command_history = lineparser.LimitLineParser(
standarddir.data(), 'cmd-history',
limit='completion.cmd_history_max_items',
@@ -164,623 +395,3 @@ def _init_misc():
path = os.path.join(standarddir.config(), 'qsettings')
for fmt in [QSettings.NativeFormat, QSettings.IniFormat]:
QSettings.setPath(fmt, QSettings.UserScope, path)
-
-
-def init(parent=None):
- """Initialize the config.
-
- Args:
- parent: The parent to pass to QObjects which get initialized.
- """
- global val, instance, key_instance
- # _init_main_config(parent)
- configdata.init()
- newconfig.init(parent)
- val = newconfig.val
- instance = newconfig.instance
- key_instance = newconfig.key_instance
- # _init_key_config(parent)
- _init_misc()
-
-
-def _get_value_transformer(mapping):
- """Get a function which transforms a value for CHANGED_OPTIONS.
-
- Args:
- mapping: A dictionary mapping old values to new values. Value is not
- transformed if the supplied value doesn't match the old value.
-
- Return:
- A function which takes a value and transforms it.
- """
- def transformer(val):
- try:
- return mapping[val]
- except KeyError:
- return val
- return transformer
-
-
-def _transform_position(val):
- """Transformer for position values."""
- mapping = {
- 'north': 'top',
- 'south': 'bottom',
- 'west': 'left',
- 'east': 'right',
- }
- try:
- return mapping[val]
- except KeyError:
- return val
-
-
-def _transform_hint_color(val):
- """Transformer for hint colors."""
- log.config.debug("Transforming hint value {}".format(val))
-
- def to_rgba(qcolor):
- """Convert a QColor to a rgba() value."""
- return 'rgba({}, {}, {}, 0.8)'.format(qcolor.red(), qcolor.green(),
- qcolor.blue())
-
- if val.startswith('-webkit-gradient'):
- pattern = re.compile(r'-webkit-gradient\(linear, left top, '
- r'left bottom, '
- r'color-stop\(0%, *([^)]*)\), '
- r'color-stop\(100%, *([^)]*)\)\)')
-
- match = pattern.fullmatch(val)
- if match:
- log.config.debug('Color groups: {}'.format(match.groups()))
- start_color = QColor(match.group(1))
- stop_color = QColor(match.group(2))
- if not start_color.isValid() or not stop_color.isValid():
- return None
-
- return ('qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {}, '
- 'stop:1 {})'.format(to_rgba(start_color),
- to_rgba(stop_color)))
- else:
- return None
- elif val.startswith('-'): # Custom CSS stuff?
- return None
- else: # Already transformed or a named color.
- return val
-
-
-def _transform_hint_font(val):
- """Transformer for fonts -> hints."""
- match = re.fullmatch(r'(.*\d+p[xt]) Monospace', val)
- if match:
- # Close enough to the old default:
- return match.group(1) + ' ${_monospace}'
- else:
- return val
-
-
-class ConfigManager(QObject):
-
- """Configuration manager for qutebrowser.
-
- Class attributes:
- KEY_ESCAPE: Chars which need escaping when they occur as first char
- in a line.
- ESCAPE_CHAR: The char to be used for escaping
- RENAMED_SECTIONS: A mapping of renamed sections, {'oldname': 'newname'}
- RENAMED_OPTIONS: A mapping of renamed options,
- {('section', 'oldname'): 'newname'}
- CHANGED_OPTIONS: A mapping of arbitrarily changed options,
- {('section', 'option'): callable}.
- The callable takes the old value and returns the new
- one.
- DELETED_OPTIONS: A (section, option) list of deleted options.
-
- Attributes:
- sections: The configuration data as an OrderedDict.
- _fname: The filename to be opened.
- _configdir: The dictionary to read the config from and save it in.
- _interpolation: A configparser.Interpolation object
- _proxies: configparser.SectionProxy objects for sections.
- _initialized: Whether the ConfigManager is fully initialized yet.
-
- Signals:
- changed: Emitted when a config option changed.
- style_changed: When style caches need to be invalidated.
- Args: the changed section and option.
- """
-
- KEY_ESCAPE = r'\#['
- ESCAPE_CHAR = '\\'
- RENAMED_SECTIONS = {
- 'permissions': 'content'
- }
- RENAMED_OPTIONS = {
- ('colors', 'tab.fg.odd'): 'tabs.fg.odd',
- ('colors', 'tab.fg.even'): 'tabs.fg.even',
- ('colors', 'tab.fg.selected'): 'tabs.fg.selected.odd',
- ('colors', 'tabs.fg.selected'): 'tabs.fg.selected.odd',
- ('colors', 'tab.bg.odd'): 'tabs.bg.odd',
- ('colors', 'tab.bg.even'): 'tabs.bg.even',
- ('colors', 'tab.bg.selected'): 'tabs.bg.selected.odd',
- ('colors', 'tabs.bg.selected'): 'tabs.bg.selected.odd',
- ('colors', 'tab.bg.bar'): 'tabs.bg.bar',
- ('colors', 'tab.indicator.start'): 'tabs.indicator.start',
- ('colors', 'tab.indicator.stop'): 'tabs.indicator.stop',
- ('colors', 'tab.indicator.error'): 'tabs.indicator.error',
- ('colors', 'tab.indicator.system'): 'tabs.indicator.system',
- ('completion', 'history-length'): 'cmd-history-max-items',
- ('colors', 'downloads.fg'): 'downloads.fg.start',
- ('ui', 'show-keyhints'): 'keyhint-blacklist',
- ('content', 'javascript-can-open-windows'):
- 'javascript-can-open-windows-automatically',
- ('colors', 'statusbar.fg.error'): 'messages.fg.error',
- ('colors', 'statusbar.bg.error'): 'messages.bg.error',
- ('colors', 'statusbar.fg.warning'): 'messages.fg.warning',
- ('colors', 'statusbar.bg.warning'): 'messages.bg.warning',
- ('colors', 'statusbar.fg.prompt'): 'prompts.fg',
- ('colors', 'statusbar.bg.prompt'): 'prompts.bg',
- ('storage', 'offline-web-application-storage'):
- 'offline-web-application-cache',
- }
- DELETED_OPTIONS = [
- ('colors', 'tab.separator'),
- ('colors', 'tabs.separator'),
- ('colors', 'tab.seperator'), # pragma: no spellcheck
- ('colors', 'tabs.seperator'), # pragma: no spellcheck
- ('colors', 'completion.item.bg'),
- ('tabs', 'indicator-space'),
- ('tabs', 'hide-auto'),
- ('tabs', 'auto-hide'),
- ('tabs', 'hide-always'),
- ('ui', 'display-statusbar-messages'),
- ('ui', 'hide-mouse-cursor'),
- ('ui', 'css-media-type'),
- ('general', 'wrap-search'),
- ('general', 'site-specific-quirks'),
- ('hints', 'opacity'),
- ('completion', 'auto-open'),
- ('storage', 'object-cache-capacities'),
- ('storage', 'offline-storage-database'),
- ('storage', 'offline-storage-default-quota'),
- ('storage', 'offline-web-application-cache-quota'),
- ('content', 'css-regions'),
- ]
- CHANGED_OPTIONS = {
- ('content', 'cookies-accept'):
- _get_value_transformer({'default': 'no-3rdparty'}),
- ('tabs', 'new-tab-position'):
- _get_value_transformer({
- 'left': 'prev',
- 'right': 'next'}),
- ('tabs', 'new-tab-position-explicit'):
- _get_value_transformer({
- 'left': 'prev',
- 'right': 'next'}),
- ('tabs', 'position'): _transform_position,
- ('tabs', 'select-on-remove'):
- _get_value_transformer({
- 'left': 'prev',
- 'right': 'next',
- 'previous': 'last-used'}),
- ('ui', 'downloads-position'): _transform_position,
- ('ui', 'remove-finished-downloads'):
- _get_value_transformer({'false': '-1', 'true': '1000'}),
- ('general', 'log-javascript-console'):
- _get_value_transformer({'false': 'none', 'true': 'debug'}),
- ('ui', 'keyhint-blacklist'):
- _get_value_transformer({'false': '*', 'true': ''}),
- ('hints', 'auto-follow'):
- _get_value_transformer({'false': 'never', 'true': 'unique-match'}),
- ('colors', 'hints.bg'): _transform_hint_color,
- ('colors', 'hints.fg'): _transform_hint_color,
- ('colors', 'hints.fg.match'): _transform_hint_color,
- ('fonts', 'hints'): _transform_hint_font,
- ('completion', 'show'):
- _get_value_transformer({'false': 'never', 'true': 'always'}),
- ('ui', 'user-stylesheet'):
- _get_value_transformer({
- 'html > ::-webkit-scrollbar { width: 0px; height: 0px; }': '',
- '::-webkit-scrollbar { width: 0px; height: 0px; }': '',
- }),
- ('general', 'default-encoding'):
- _get_value_transformer({'': 'iso-8859-1'}),
- ('contents', 'cache-size'):
- _get_value_transformer({'52428800': ''}),
- ('storage', 'maximum-pages-in-cache'):
- _get_value_transformer({'': '0'}),
- ('fonts', 'web-size-minimum'):
- _get_value_transformer({'': '0'}),
- ('fonts', 'web-size-minimum-logical'):
- _get_value_transformer({'': '6'}),
- ('fonts', 'web-size-default'):
- _get_value_transformer({'': '16'}),
- ('fonts', 'web-size-default-fixed'):
- _get_value_transformer({'': '13'}),
- }
-
- changed = pyqtSignal(str, str)
- style_changed = pyqtSignal(str, str)
-
- def __init__(self, parent=None):
- super().__init__(parent)
- self._initialized = False
- self._configdir = None
- self._fname = None
- self.sections = configdata.data()
- self._interpolation = configparser.ExtendedInterpolation()
- self._proxies = {}
- for sectname in self.sections:
- self._proxies[sectname] = SectionProxy(self, sectname)
-
- def __getitem__(self, key):
- """Get a section from the config."""
- return self._proxies[key]
-
- def __repr__(self):
- return utils.get_repr(self, fname=self._fname)
-
- def __str__(self):
- """Get the whole config as a string."""
- lines = configdata.FIRST_COMMENT.strip('\n').splitlines()
- for sectname, sect in self.sections.items():
- lines += ['\n'] + self._str_section_desc(sectname)
- lines.append('[{}]'.format(sectname))
- lines += self._str_items(sectname, sect)
- return '\n'.join(lines) + '\n'
-
- def _str_section_desc(self, sectname):
- """Get the section description string for sectname."""
- wrapper = textwrapper.TextWrapper()
- lines = []
- seclines = configdata.SECTION_DESC[sectname].splitlines()
- for secline in seclines:
- if 'http://' in secline or 'https://' in secline:
- lines.append('# ' + secline)
- else:
- lines += wrapper.wrap(secline)
- return lines
-
- def _str_items(self, sectname, sect):
- """Get the option items as string for sect."""
- lines = []
- for optname, option in sect.items():
- value = option.value(startlayer='conf')
- for c in self.KEY_ESCAPE:
- if optname.startswith(c):
- optname = optname.replace(c, self.ESCAPE_CHAR + c, 1)
- # configparser can't handle = in keys :(
- optname = optname.replace('=', '')
- keyval = '{} = {}'.format(optname, value)
- lines += self._str_option_desc(sectname, sect, optname, option)
- lines.append(keyval)
- return lines
-
- def _str_option_desc(self, sectname, sect, optname, option):
- """Get the option description strings for a single option."""
- wrapper = textwrapper.TextWrapper(initial_indent='#' + ' ' * 5,
- subsequent_indent='#' + ' ' * 5)
- lines = []
- if not getattr(sect, 'descriptions', None):
- return lines
-
- lines.append('')
- typestr = ' ({})'.format(option.typ.get_name())
- lines.append("# {}{}:".format(optname, typestr))
-
- try:
- desc = self.sections[sectname].descriptions[optname]
- except KeyError:
- log.config.exception("No description for {}.{}!".format(
- sectname, optname))
- return []
- for descline in desc.splitlines():
- lines += wrapper.wrap(descline)
- valid_values = option.typ.get_valid_values()
- if valid_values is not None:
- if valid_values.descriptions:
- for val in valid_values:
- desc = valid_values.descriptions[val]
- lines += wrapper.wrap(" {}: {}".format(val, desc))
- else:
- lines += wrapper.wrap("Valid values: {}".format(', '.join(
- valid_values)))
- lines += wrapper.wrap("Default: {}".format(
- option.values['default']))
- return lines
-
- def _get_real_sectname(self, cp, sectname):
- """Get an old or new section name based on a configparser.
-
- This checks if sectname is in cp, and if not, migrates it if needed and
- tries again.
-
- Args:
- cp: The configparser to check.
- sectname: The new section name.
-
- Returns:
- The section name in the configparser as a string, or None if the
- configparser doesn't contain the section.
- """
- reverse_renamed_sections = {v: k for k, v in
- self.RENAMED_SECTIONS.items()}
- if sectname in reverse_renamed_sections:
- old_sectname = reverse_renamed_sections[sectname]
- else:
- old_sectname = sectname
- if old_sectname in cp:
- return old_sectname
- elif sectname in cp:
- return sectname
- else:
- return None
-
- def _from_cp(self, cp, relaxed=False):
- """Read the config from a configparser instance.
-
- Args:
- cp: The configparser instance to read the values from.
- relaxed: Whether to ignore inexistent sections/options.
- """
- for sectname in cp:
- if sectname in self.RENAMED_SECTIONS:
- sectname = self.RENAMED_SECTIONS[sectname]
- if sectname != 'DEFAULT' and sectname not in self.sections:
- if not relaxed:
- raise configexc.NoSectionError(sectname)
- for sectname in self.sections:
- self._from_cp_section(sectname, cp, relaxed)
-
- def _from_cp_section(self, sectname, cp, relaxed):
- """Read a single section from a configparser instance.
-
- Args:
- sectname: The name of the section to read.
- cp: The configparser instance to read the values from.
- relaxed: Whether to ignore inexistent options.
- """
- real_sectname = self._get_real_sectname(cp, sectname)
- if real_sectname is None:
- return
- for k, v in cp[real_sectname].items():
- if k.startswith(self.ESCAPE_CHAR):
- k = k[1:]
-
- if (sectname, k) in self.DELETED_OPTIONS:
- continue
- if (sectname, k) in self.RENAMED_OPTIONS:
- k = self.RENAMED_OPTIONS[sectname, k]
- if (sectname, k) in self.CHANGED_OPTIONS:
- func = self.CHANGED_OPTIONS[(sectname, k)]
- new_v = func(v)
- if new_v is None:
- exc = configexc.ValidationError(
- v, "Could not automatically migrate the given value")
- exc.section = sectname
- exc.option = k
- raise exc
-
- v = new_v
-
- try:
- self.set('conf', sectname, k, v, validate=False)
- except configexc.NoOptionError:
- if relaxed:
- pass
- else:
- raise
-
- def _validate_all(self):
- """Validate all values set in self._from_cp."""
- for sectname, sect in self.sections.items():
- mapping = {key: val.value() for key, val in sect.values.items()}
- for optname, opt in sect.items():
- interpolated = self._interpolation.before_get(
- self, sectname, optname, opt.value(), mapping)
- try:
- opt.typ.validate(interpolated)
- except configexc.ValidationError as e:
- e.section = sectname
- e.option = optname
- raise
-
- def _changed(self, sectname, optname):
- """Notify other objects the config has changed."""
- log.config.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._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._changed(changed_sect, optname)
- # Options in any section and ${sectname:optname} interpolation.
- for sectname, sect in self.sections.items():
- for optname, option in sect.items():
- if ('${' + changed_sect + ':' + changed_opt + '}' in
- option.value()):
- self._changed(sectname, optname)
-
- def read(self, configdir, fname, relaxed=False):
- """Read the config from the given directory/file."""
- self._fname = fname
- self._configdir = configdir
- parser = ini.ReadConfigParser(configdir, fname)
- self._from_cp(parser, relaxed)
- self._initialized = True
- self._validate_all()
-
- def items(self, sectname, raw=True):
- """Get a list of (optname, value) tuples for a section.
-
- Implemented for configparser interpolation compatibility
-
- 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():
- items.append((optname, option.value()))
- return items
-
- def has_option(self, sectname, optname):
- """Check if option exists in section.
-
- Args:
- sectname: The section name.
- optname: The option name
-
- Return:
- True if the option and section exist, False otherwise.
- """
- if sectname not in self.sections:
- return False
- return optname in self.sections[sectname]
-
- def remove_option(self, sectname, optname):
- """Remove an option.
-
- Args:
- sectname: The section where to remove an option.
- optname: The option name to remove.
-
- Return:
- True if the option existed, False otherwise.
- """
- try:
- sectdict = self.sections[sectname]
- except KeyError:
- raise configexc.NoSectionError(sectname)
- optname = self.optionxform(optname)
- existed = optname in sectdict
- if existed:
- sectdict.delete(optname)
- self.get.cache_clear()
- return existed
-
- def set(self, layer, sectname, optname, value, validate=True):
- """Set an option.
-
- Args:
- layer: A layer name as string (conf/temp/default).
- sectname: The name of the section to change.
- optname: The name of the option to change.
- value: The new value.
- validate: Whether to validate the value immediately.
- """
- try:
- value = self._interpolation.before_set(self, sectname, optname,
- value)
- except ValueError as e:
- raise configexc.InterpolationSyntaxError(optname, sectname, str(e))
- try:
- sect = self.sections[sectname]
- except KeyError:
- raise configexc.NoSectionError(sectname)
- mapping = {key: val.value() for key, val in sect.values.items()}
-
- if validate:
- interpolated = self._interpolation.before_get(
- self, sectname, optname, value, mapping)
- try:
- allowed_backends = sect.values[optname].backends
- except KeyError:
- # Will be handled later in .setv()
- pass
- else:
- if (allowed_backends is not None and
- objects.backend not in allowed_backends):
- raise configexc.BackendError(objects.backend)
- else:
- interpolated = None
-
- try:
- sect.setv(layer, optname, value, interpolated)
- except KeyError:
- raise configexc.NoOptionError(optname, sectname)
- else:
- if self._initialized:
- self._after_set(sectname, optname)
-
- def save(self):
- """Save the config file."""
- configfile = os.path.join(self._configdir, self._fname)
- log.destroy.debug("Saving config to {}".format(configfile))
- with qtutils.savefile_open(configfile) as f:
- f.write(str(self))
-
- def optionxform(self, val):
- """Implemented to be compatible with ConfigParser interpolation."""
- return val
-
-
-class SectionProxy(collections.abc.MutableMapping):
-
- """A proxy for a single section from a config.
-
- Attributes:
- _conf: The Config object.
- _name: The section name.
- """
-
- def __init__(self, conf, name):
- """Create a view on a section.
-
- Args:
- conf: The Config object.
- name: The section name.
- """
- self.conf = conf
- self.name = name
-
- def __repr__(self):
- return utils.get_repr(self, name=self.name)
-
- def __getitem__(self, key):
- if not self.conf.has_option(self.name, key):
- raise KeyError(key)
- return self.conf.get(self.name, key)
-
- def __setitem__(self, key, value):
- return self.conf.set('conf', self.name, key, value)
-
- def __delitem__(self, key):
- if not (self.conf.has_option(self.name, key) and
- self.conf.remove_option(self.name, key)):
- raise KeyError(key)
-
- def __contains__(self, key):
- return self.conf.has_option(self.name, key)
-
- def __len__(self):
- return len(self._options())
-
- def __iter__(self):
- return self._options().__iter__()
-
- def _options(self):
- """Get the option keys from this section."""
- return self.conf.sections[self.name].keys()
-
- def get(self, optname, *, raw=False): # pylint: disable=arguments-differ
- """Get a value from this section.
-
- We deliberately don't support the default argument here, but have a raw
- argument instead.
-
- Args:
- optname: The option name to get.
- raw: Whether to get a raw value or not.
- """
- return self.conf.get(self.name, optname, raw=raw)
diff --git a/qutebrowser/config/newconfig.py b/qutebrowser/config/newconfig.py
deleted file mode 100644
index 5d8b02cca..000000000
--- a/qutebrowser/config/newconfig.py
+++ /dev/null
@@ -1,335 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2014-2017 Florian Bruhin (The Compiler)
-#
-# 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 .
-
-"""New qutebrowser configuration code."""
-
-import contextlib
-import functools
-
-from PyQt5.QtCore import pyqtSignal, QObject, QUrl
-
-from qutebrowser.config import configdata, configexc, configtypes
-from qutebrowser.utils import utils, objreg, message
-from qutebrowser.commands import cmdexc, cmdutils
-
-# An easy way to access the config from other code via config.val.foo
-val = None
-instance = None
-
-_change_filters = []
-
-
-class change_filter: # pylint: disable=invalid-name
-
- """Decorator to filter calls based on a config section/option matching.
-
- This could also be a function, but as a class (with a "wrong" name) it's
- much cleaner to implement.
-
- Attributes:
- _option: An option or prefix to be filtered
- _function: Whether a function rather than a method is decorated.
- """
-
- def __init__(self, option, function=False):
- """Save decorator arguments.
-
- Gets called on parse-time with the decorator arguments.
-
- Args:
- option: The option to be filtered.
- function: Whether a function rather than a method is decorated.
- """
- self._option = option
- self._function = function
- _change_filters.append(self)
-
- def validate(self):
- """Make sure the configured option or prefix exists.
-
- We can't do this in __init__ as configdata isn't ready yet.
- """
- if (self._option not in configdata.DATA and
- not configdata.is_valid_prefix(self._option)):
- raise configexc.NoOptionError(self._option)
-
- def _check_match(self, option):
- """Check if the given option matches the filter."""
- if option is None:
- # Called directly, not from a config change event.
- return True
- elif option == self._option:
- return True
- elif option.startswith(self._option + '.'):
- # prefix match
- return True
- else:
- return False
-
- def __call__(self, func):
- """Filter calls to the decorated function.
-
- Gets called when a function should be decorated.
-
- Adds a filter which returns if we're not interested in the change-event
- and calls the wrapped function if we are.
-
- We assume the function passed doesn't take any parameters.
-
- Args:
- func: The function to be decorated.
-
- Return:
- The decorated function.
- """
- if self._function:
- @functools.wraps(func)
- def wrapper(option=None):
- if self._check_match(option):
- return func()
- else:
- @functools.wraps(func)
- def wrapper(wrapper_self, option=None):
- if self._check_match(option):
- return func(wrapper_self)
-
- return wrapper
-
-
-class NewKeyConfig:
-
- def get_reverse_bindings_for(self, section):
- """Get a dict of commands to a list of bindings for the section."""
- cmd_to_keys = {}
- bindings = val.bindings.commands[section]
- if bindings is None:
- return cmd_to_keys
- for key, full_cmd in bindings.items():
- for cmd in full_cmd.split(';;'):
- cmd = cmd.strip()
- cmd_to_keys.setdefault(cmd, [])
- # put special bindings last
- if utils.is_special_key(key):
- cmd_to_keys[cmd].append(key)
- else:
- cmd_to_keys[cmd].insert(0, key)
- return cmd_to_keys
-
-
-class ConfigCommands:
-
- def __init__(self, config):
- self._config = config
-
- @cmdutils.register(instance='config-commands', star_args_optional=True)
- @cmdutils.argument('win_id', win_id=True)
- def set(self, win_id, option=None, *values, temp=False, print_=False):
- """Set an option.
-
- If the option name ends with '?', the value of the option is shown
- instead.
-
- If the option name ends with '!' and it is a boolean value, toggle it.
-
- //
-
- Args:
- option: The name of the option.
- values: The value to set, or the values to cycle through.
- temp: Set value temporarily.
- print_: Print the value after setting.
- """
- # FIXME:conf write to YAML if temp isn't used!
- if option is None:
- tabbed_browser = objreg.get('tabbed-browser', scope='window',
- window=win_id)
- tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
- return
-
- if option.endswith('?') and option != '?':
- self._print_value(option[:-1])
- return
-
- with self._handle_config_error():
- if option.endswith('!') and option != '!' and not values:
- # Handle inversion as special cases of the cycle code path
- option = option[:-1]
- opt = self._config.get_opt(option)
- if opt.typ is configtypes.Bool:
- values = ['false', 'true']
- else:
- raise cmdexc.CommandError(
- "set: Attempted inversion of non-boolean value.")
- elif not values:
- raise cmdexc.CommandError("set: The following arguments "
- "are required: value")
- self._set_next(option, values)
-
- if print_:
- self._print_value(option)
-
- def _print_value(self, option):
- """Print the value of the given option."""
- with self._handle_config_error():
- val = self._config.get_str(option)
- message.info("{} = {}".format(option, val))
-
- def _set_next(self, option, values):
- """Set the next value out of a list of values."""
- if len(values) == 1:
- # If we have only one value, just set it directly (avoid
- # breaking stuff like aliases or other pseudo-settings)
- self._config.set(option, values[0])
- return
-
- # Use the next valid value from values, or the first if the current
- # value does not appear in the list
- val = self._config.get_str(option)
- try:
- idx = values.index(str(val))
- idx = (idx + 1) % len(values)
- value = values[idx]
- except ValueError:
- value = values[0]
- self._config.set(option, value)
-
- @contextlib.contextmanager
- def _handle_config_error(self):
- """Catch errors in set_command and raise CommandError."""
- try:
- yield
- except (configexc.NoOptionError, configexc.ValidationError) as e:
- raise cmdexc.CommandError("set: {}".format(e))
- except configexc.Error as e:
- raise cmdexc.CommandError("set: {} - {}".format(
- e.__class__.__name__, e))
-
-
-class NewConfigManager(QObject):
-
- changed = pyqtSignal(str) # FIXME:conf stub...
-
- def __init__(self, parent=None):
- super().__init__(parent)
- self.options = {}
- self._values = {} # FIXME:conf stub
-
- def read_defaults(self):
- for name, option in configdata.DATA.items():
- self.options[name] = option
-
- def get_opt(self, name):
- try:
- return self.options[name]
- except KeyError:
- raise configexc.NoOptionError(name)
-
- def get(self, name):
- opt = self.get_opt(name)
- value = self._values.get(name, opt.default)
- return opt.typ.to_py(value)
-
- def get_str(self, name):
- opt = self.get_opt(name)
- return opt.typ.to_str(opt.default)
-
- def set(self, name, value):
- # FIXME:conf stub
- try:
- opt = self.options[name]
- except KeyError:
- raise configexc.NoOptionError(name)
- self._values[name] = opt.typ.from_str(value)
- self.changed.emit(name)
-
- def dump_userconfig(self):
- """Get the part of the config which was changed by the user.
-
- Return:
- The changed config part as string.
- """
- lines = ['{} = {}'.format(optname, value)
- for optname, value in self._values.items()]
- if not lines:
- lines = ['']
- return '\n'.join(lines)
-
-
-class ConfigContainer:
-
- """An object implementing config access via __getattr__.
-
- Attributes:
- _manager: The ConfigManager object.
- _prefix: The __getattr__ chain leading up to this object.
- """
-
- def __init__(self, manager, prefix=''):
- self._manager = manager
- self._prefix = prefix
-
- def __repr__(self):
- return utils.get_repr(self, constructor=True, manager=self._manager,
- prefix=self._prefix)
-
- def __getattr__(self, attr):
- """Get an option or a new ConfigContainer with the added prefix.
-
- If we get an option which exists, we return the value for it.
- If we get a part of an option name, we return a new ConfigContainer.
-
- Those two never overlap as configdata.py ensures there are no shadowing
- options.
- """
- name = self._join(attr)
- if configdata.is_valid_prefix(name):
- return ConfigContainer(manager=self._manager, prefix=name)
- try:
- return self._manager.get(name)
- except configexc.NoOptionError as e:
- # If it's not a valid prefix - re-raise to improve error text.
- raise configexc.NoOptionError(name)
-
- def __setattr__(self, attr, value):
- if attr.startswith('_'):
- return super().__setattr__(attr, value)
- self._handler(self._join(attr), value)
-
- def _join(self, attr):
- if self._prefix:
- return '{}.{}'.format(self._prefix, attr)
- else:
- return attr
-
-
-def init(parent):
- new_config = NewConfigManager(parent)
- new_config.read_defaults()
- objreg.register('config', new_config)
-
- config_commands = ConfigCommands(new_config)
- objreg.register('config-commands', config_commands)
-
- global val, instance, key_instance
- val = ConfigContainer(new_config)
- instance = new_config
- key_instance = NewKeyConfig()
-
- for cf in _change_filters:
- cf.validate()
diff --git a/qutebrowser/config/parsers/ini.py b/qutebrowser/config/parsers/ini.py
deleted file mode 100644
index 0ae485f4b..000000000
--- a/qutebrowser/config/parsers/ini.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2014-2017 Florian Bruhin (The Compiler)
-#
-# 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 .
-
-"""Parsers for INI-like config files, based on Python's ConfigParser."""
-
-import os
-import os.path
-import configparser
-
-from qutebrowser.utils import log, utils, qtutils
-
-
-class ReadConfigParser(configparser.ConfigParser):
-
- """Our own ConfigParser subclass to read the main config.
-
- Attributes:
- _configdir: The directory to read the config from.
- _fname: The filename of the config.
- _configfile: The config file path.
- """
-
- def __init__(self, configdir, fname):
- """Config constructor.
-
- Args:
- configdir: Directory to read the config from.
- fname: Filename of the config file.
- """
- super().__init__(interpolation=None, comment_prefixes='#')
- self.optionxform = lambda opt: opt # be case-insensitive
- self._configdir = configdir
- self._fname = fname
- self._configfile = os.path.join(self._configdir, fname)
-
- if not os.path.isfile(self._configfile):
- return
- log.init.debug("Reading config from {}".format(self._configfile))
- self.read(self._configfile, encoding='utf-8')
-
- def __repr__(self):
- return utils.get_repr(self, constructor=True,
- configdir=self._configdir, fname=self._fname)
-
-
-class ReadWriteConfigParser(ReadConfigParser):
-
- """ConfigParser subclass used for auxiliary config files."""
-
- def save(self):
- """Save the config file."""
- if self._configdir is None:
- return
- if not os.path.exists(self._configdir):
- os.makedirs(self._configdir, 0o755)
- log.destroy.debug("Saving config to {}".format(self._configfile))
- with qtutils.savefile_open(self._configfile) as f:
- self.write(f)
diff --git a/qutebrowser/config/sections.py b/qutebrowser/config/sections.py
deleted file mode 100644
index 04a735647..000000000
--- a/qutebrowser/config/sections.py
+++ /dev/null
@@ -1,231 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2014-2017 Florian Bruhin (The Compiler)
-#
-# 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 .
-
-"""Setting sections used for qutebrowser."""
-
-import collections
-
-from qutebrowser.config import value as confvalue
-
-
-class Section:
-
- """Base class for KeyValue/ValueList sections.
-
- Attributes:
- _readonly: Whether this section is read-only.
- values: An OrderedDict with key as index and value as value.
- key: string
- value: SettingValue
- descriptions: A dict with the description strings for the keys.
- """
-
- def __init__(self):
- self.values = None
- self.descriptions = {}
- self._readonly = False
-
- def __getitem__(self, key):
- """Get the value for key.
-
- Args:
- key: The key to get a value for, as a string.
-
- Return:
- The value, as value class.
- """
- return self.values[key]
-
- def __iter__(self):
- """Iterate over all set values."""
- return iter(self.values)
-
- def __bool__(self):
- """Get boolean state of section."""
- return bool(self.values)
-
- def __contains__(self, key):
- """Return whether the section contains a given key."""
- return key in self.values
-
- def items(self):
- """Get dict items."""
- return self.values.items()
-
- def keys(self):
- """Get value keys."""
- return self.values.keys()
-
- def delete(self, key):
- """Delete item with given key."""
- del self.values[key]
-
- def setv(self, layer, key, value, interpolated):
- """Set the value on a layer.
-
- Args:
- layer: The layer to set the value on, an element name of the
- ValueLayers dict.
- key: The key of the element to set.
- value: The value to set.
- interpolated: The interpolated value, for checking, or None.
- """
- raise NotImplementedError
-
- def dump_userconfig(self):
- """Dump the part of the config which was changed by the user.
-
- Return:
- A list of (key, valuestr) tuples.
- """
- raise NotImplementedError
-
-
-class KeyValue(Section):
-
- """Representation of a section with ordinary key-value mappings.
-
- This is a section which contains normal "key = value" pairs with a fixed
- set of keys.
- """
-
- def __init__(self, *defaults, readonly=False):
- """Constructor.
-
- Args:
- *defaults: A (key, value, description) list of defaults.
- readonly: Whether this config is readonly.
- """
- super().__init__()
- self._readonly = readonly
- if not defaults:
- return
- self.values = collections.OrderedDict()
- for (k, v, desc) in defaults:
- assert k not in self.values, k
- self.values[k] = v
- self.descriptions[k] = desc
-
- def setv(self, layer, key, value, interpolated):
- if self._readonly:
- raise ValueError("Trying to modify a read-only config!")
- self.values[key].setv(layer, value, interpolated)
-
- def dump_userconfig(self):
- changed = []
- for k, v in self.items():
- vals = v.values
- if vals['temp'] is not None and vals['temp'] != vals['default']:
- changed.append((k, vals['temp']))
- elif vals['conf'] is not None and vals['conf'] != vals['default']:
- changed.append((k, vals['conf']))
- return changed
-
-
-class ValueList(Section):
-
- """This class represents a section with a list key-value settings.
-
- These are settings inside sections which don't have fixed keys, but instead
- have a dynamic list of "key = value" pairs, like key bindings or
- searchengines.
-
- They basically consist of two different SettingValues.
-
- Attributes:
- layers: An OrderedDict of the config layers.
- keytype: The type to use for the key (only used for validating)
- valtype: The type to use for the value.
- _ordered_value_cache: A ChainMap-like OrderedDict of all values.
- _readonly: Whether this section is read-only.
- """
-
- def __init__(self, keytype, valtype, *defaults, readonly=False):
- """Wrap types over default values. Take care when overriding this.
-
- Args:
- keytype: The type instance to be used for keys.
- valtype: The type instance to be used for values.
- *defaults: A (key, value) list of default values.
- readonly: Whether this config is readonly.
- """
- super().__init__()
- self._readonly = readonly
- self._ordered_value_cache = None
- self.keytype = keytype
- self.valtype = valtype
- self.layers = collections.OrderedDict([
- ('default', collections.OrderedDict()),
- ('conf', collections.OrderedDict()),
- ('temp', collections.OrderedDict()),
- ])
- defaultlayer = self.layers['default']
- for key, value in defaults:
- assert key not in defaultlayer, key
- defaultlayer[key] = confvalue.SettingValue(valtype, value)
- self.values = collections.ChainMap(
- self.layers['temp'], self.layers['conf'], self.layers['default'])
-
- def _ordered_values(self):
- """Get ordered values in layers.
-
- This is more expensive than the ChainMap, but we need this for
- iterating/items/etc. when order matters.
- """
- if self._ordered_value_cache is None:
- self._ordered_value_cache = collections.OrderedDict()
- for layer in self.layers.values():
- self._ordered_value_cache.update(layer)
- return self._ordered_value_cache
-
- def setv(self, layer, key, value, interpolated):
- if self._readonly:
- raise ValueError("Trying to modify a read-only config!")
- self.keytype.validate(key)
- if key in self.layers[layer]:
- self.layers[layer][key].setv(layer, value, interpolated)
- else:
- val = confvalue.SettingValue(self.valtype)
- val.setv(layer, value, interpolated)
- self.layers[layer][key] = val
- self._ordered_value_cache = None
-
- def dump_userconfig(self):
- changed = []
- mapping = collections.ChainMap(self.layers['temp'],
- self.layers['conf'])
- for k, v in mapping.items():
- try:
- if v.value() != self.layers['default'][k].value():
- changed.append((k, v.value()))
- except KeyError:
- changed.append((k, v.value()))
- return changed
-
- def __iter__(self):
- """Iterate over all set values."""
- return self._ordered_values().__iter__()
-
- def items(self):
- """Get dict items."""
- return self._ordered_values().items()
-
- def keys(self):
- """Get value keys."""
- return self._ordered_values().keys()
diff --git a/qutebrowser/config/textwrapper.py b/qutebrowser/config/textwrapper.py
deleted file mode 100644
index b5744f60b..000000000
--- a/qutebrowser/config/textwrapper.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2014-2017 Florian Bruhin (The Compiler)
-#
-# 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 .
-
-"""Textwrapper used for config files."""
-
-import textwrap
-
-
-class TextWrapper(textwrap.TextWrapper):
-
- """Text wrapper customized to be used in configs."""
-
- def __init__(self, **kwargs):
- kw = {
- 'width': 72,
- 'replace_whitespace': False,
- 'break_long_words': False,
- 'break_on_hyphens': False,
- 'initial_indent': '# ',
- 'subsequent_indent': '# ',
- }
- kw.update(kwargs)
- super().__init__(**kw)
diff --git a/qutebrowser/config/value.py b/qutebrowser/config/value.py
deleted file mode 100644
index b23674606..000000000
--- a/qutebrowser/config/value.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2014-2017 Florian Bruhin (The Compiler)
-#
-# 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 .
-
-"""A single value (with multiple layers possibly) in the config."""
-
-import collections
-
-
-class SettingValue:
-
- """Base class for setting values.
-
- Intended to be sub-classed by config value "types".
-
- Attributes:
- typ: A BaseType subclass instance.
- value: (readonly property) The currently valid, most important value.
- values: An OrderedDict with the values on different layers, with the
- most significant layer first.
- """
-
- def __init__(self, typ, default=None, *, backends=None):
- """Constructor.
-
- Args:
- typ: The BaseType to use.
- default: Raw value to set.
- backend: A list of usertypes.Backend enum members to mark this
- setting as unsupported with other backends.
- """
- self.typ = typ
- self.values = collections.OrderedDict.fromkeys(
- ['temp', 'conf', 'default'])
- self.values['default'] = default
- self.backends = backends
-
- def __str__(self):
- """Get raw string value."""
- return self.value()
-
- def default(self):
- """Get the default value."""
- return self.values['default']
-
- def getlayers(self, startlayer):
- """Get a dict of values starting with startlayer.
-
- Args:
- startlayer: The first layer to include.
- """
- idx = list(self.values.keys()).index(startlayer)
- d = collections.OrderedDict(list(self.values.items())[idx:])
- return d
-
- def value(self, startlayer=None):
- """Get the first valid value starting from startlayer.
-
- Args:
- startlayer: The first layer to include.
- """
- if startlayer is None:
- d = self.values
- else:
- d = self.getlayers(startlayer)
- for val in d.values():
- if val is not None:
- return val
- raise ValueError("No valid config value found!")
-
- def transformed(self):
- """Get the transformed value."""
- return self.typ.transform(self.value())
-
- def setv(self, layer, value, interpolated):
- """Set the value on a layer.
-
- Args:
- layer: The layer to set the value on, an element name of the
- ValueLayers dict.
- value: The value to set.
- interpolated: The interpolated value, for typechecking (or None).
- """
- if interpolated is not None:
- self.typ.validate(interpolated)
- self.values[layer] = value
diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py
index c897b9f16..da5daea10 100644
--- a/tests/unit/config/test_config.py
+++ b/tests/unit/config/test_config.py
@@ -81,29 +81,6 @@ class TestConfigParser:
with pytest.raises(configexc.ValidationError):
objects.cfg._validate_all()
- @pytest.mark.parametrize('config, sect1, opt1, sect2, opt2', [
- # Same section
- ({'general': {'ignore-case': 'false',
- 'private-browsing': '${ignore-case}'}},
- 'general', 'ignore-case', 'general', 'private-browsing'),
- # Across sections
- ({'general': {'ignore-case': '${network:do-not-track}'},
- 'network': {'do-not-track': 'false'}},
- 'general', 'ignore-case', 'network', 'do-not-track'),
- ])
- def test_interpolation(self, objects, config, sect1, opt1, sect2, opt2):
- objects.cp.read_dict(config)
- objects.cfg._from_cp(objects.cp)
- assert not objects.cfg.get(sect1, opt1)
- assert not objects.cfg.get(sect2, opt2)
-
- def test_invalid_interpolation(self, objects):
- """Test an invalid interpolation."""
- objects.cp.read_dict({'general': {'ignore-case': '${foo}'}})
- objects.cfg._from_cp(objects.cp)
- with pytest.raises(configparser.InterpolationError):
- objects.cfg._validate_all()
-
@pytest.mark.parametrize('config, exception', [
# Invalid interpolation syntax
({'general': {'ignore-case': '${'}},
@@ -131,26 +108,6 @@ class TestConfigParser:
with pytest.raises(exception):
objects.cfg.get(sect, opt)
- def test_fallback(self, objects):
- """Test getting an option with fallback.
-
- This is done during interpolation in later Python 3.4 versions.
-
- See https://github.com/qutebrowser/qutebrowser/issues/968
- """
- assert objects.cfg.get('general', 'blabla', fallback='blub') == 'blub'
-
- def test_sectionproxy(self, objects):
- """Test getting an option via the section proxy."""
- objects.cp.read_dict({'general': {'ignore-case': 'false'}})
- objects.cfg._from_cp(objects.cp)
- assert not objects.cfg['general'].get('ignore-case')
-
- def test_sectionproxy_keyerror(self, objects):
- """Test getting an inexistent option via the section proxy."""
- with pytest.raises(configexc.NoOptionError):
- objects.cfg['general'].get('blahblahblub')
-
@pytest.mark.parametrize('old_sect, new_sect',
config.ConfigManager.RENAMED_SECTIONS.items())
def test_renamed_sections(self, old_sect, new_sect):
@@ -184,62 +141,6 @@ class TestConfigParser:
assert objects.cfg.get('general', 'save-session')
-class TestTransformers:
-
- """Test value transformers in CHANGED_OPTIONS."""
-
- @pytest.mark.parametrize('val, expected', [('a', 'b'), ('c', 'c')])
- def test_get_value_transformer(self, val, expected):
- func = config._get_value_transformer({'a': 'b'})
- assert func(val) == expected
-
- @pytest.mark.parametrize('val, expected', [
- ('top', 'top'),
- ('north', 'top'),
- ('south', 'bottom'),
- ('west', 'left'),
- ('east', 'right'),
- ])
- def test_position(self, val, expected):
- func = config._transform_position
- assert func(val) == expected
-
- OLD_GRADIENT = ('-webkit-gradient(linear, left top, left bottom, '
- 'color-stop(0%,{}), color-stop(100%,{}))')
- NEW_GRADIENT = ('qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {}, '
- 'stop:1 {})')
-
- @pytest.mark.parametrize('val, expected', [
- ('-unknown-stuff', None),
- ('blue', 'blue'),
- ('rgba(1, 2, 3, 4)', 'rgba(1, 2, 3, 4)'),
- ('-webkit-gradient(unknown)', None),
- (OLD_GRADIENT.format('blah', 'blah'), None),
- (OLD_GRADIENT.format('red', 'green'),
- NEW_GRADIENT.format('rgba(255, 0, 0, 0.8)', 'rgba(0, 128, 0, 0.8)')),
- (OLD_GRADIENT.format(' red', ' green'),
- NEW_GRADIENT.format('rgba(255, 0, 0, 0.8)', 'rgba(0, 128, 0, 0.8)')),
- (OLD_GRADIENT.format('#101010', ' #202020'),
- NEW_GRADIENT.format('rgba(16, 16, 16, 0.8)',
- 'rgba(32, 32, 32, 0.8)')),
- (OLD_GRADIENT.format('#666', ' #777'),
- NEW_GRADIENT.format('rgba(102, 102, 102, 0.8)',
- 'rgba(119, 119, 119, 0.8)')),
- (OLD_GRADIENT.format('red', 'green') + 'more stuff', None),
- ])
- def test_hint_color(self, val, expected):
- assert config._transform_hint_color(val) == expected
-
- @pytest.mark.parametrize('val, expected', [
- ('bold 12pt Monospace', 'bold 12pt ${_monospace}'),
- ('23pt Monospace', '23pt ${_monospace}'),
- ('bold 12pt ${_monospace}', 'bold 12pt ${_monospace}'),
- ('bold 12pt Comic Sans MS', 'bold 12pt Comic Sans MS'),
- ])
- def test_hint_font(self, val, expected):
- assert config._transform_hint_font(val) == expected
-
-
class TestKeyConfigParser:
"""Test config.parsers.keyconf.KeyConfigParser."""
diff --git a/tests/unit/config/test_textwrapper.py b/tests/unit/config/test_textwrapper.py
deleted file mode 100644
index a654f9367..000000000
--- a/tests/unit/config/test_textwrapper.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2015-2017 Florian Bruhin (The Compiler)
-#
-# 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 .
-
-"""Tests for config.textwrapper."""
-
-from qutebrowser.config import textwrapper
-
-
-def test_default_args():
- wrapper = textwrapper.TextWrapper()
- assert wrapper.width == 72
- assert not wrapper.replace_whitespace
- assert not wrapper.break_long_words
- assert not wrapper.break_on_hyphens
- assert wrapper.initial_indent == '# '
- assert wrapper.subsequent_indent == '# '
-
-
-def test_custom_args():
- wrapper = textwrapper.TextWrapper(drop_whitespace=False)
- assert wrapper.width == 72
- assert not wrapper.drop_whitespace