qutebrowser/qutebrowser/config/configutils.py

187 lines
5.7 KiB
Python
Raw Normal View History

2018-02-19 04:54:12 +01:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 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/>.
"""Utilities and data structures used by various config code."""
import attr
2018-02-20 06:21:11 +01:00
from qutebrowser.utils import utils
from qutebrowser.config import configexc
2018-02-20 06:21:11 +01:00
2018-02-19 04:54:12 +01:00
class _UnsetObject:
"""Sentinel object."""
__slots__ = ()
def __repr__(self):
return '<UNSET>'
UNSET = _UnsetObject()
2018-02-19 04:54:12 +01:00
@attr.s
class ScopedValue:
"""A configuration value which is valid for a UrlPattern.
Attributes:
value: The value itself.
pattern: The UrlPattern for the value, or None for global values.
"""
value = attr.ib()
pattern = attr.ib()
class Values:
"""A collection of values for a single setting.
Currently, this is a list and iterates through all possible ScopedValues to
find matching ones.
In the future, it should be possible to optimize this by doing
pre-selection based on hosts, by making this a dict mapping the
non-wildcard part of the host to a list of matching ScopedValues.
That way, when searching for a setting for sub.example.com, we only have to
check 'sub.example.com', 'example.com', '.com' and '' instead of checking
all ScopedValues for the given setting.
Attributes:
opt: The Option being customized.
2018-02-19 04:54:12 +01:00
"""
def __init__(self, opt, values=None):
self.opt = opt
self._values = values or []
2018-02-19 04:54:12 +01:00
2018-02-20 06:21:11 +01:00
def __repr__(self):
return utils.get_repr(self, opt=self.opt, values=self._values,
constructor=True)
def __str__(self):
"""Get the values as human-readable string."""
if not self:
return '{}: <unchanged>'.format(self.opt.name)
lines = []
for scoped in self._values:
str_value = self.opt.typ.to_str(scoped.value)
if scoped.pattern is None:
lines.append('{} = {}'.format(self.opt.name, str_value))
else:
lines.append('{}: {} = {}'.format(
scoped.pattern, self.opt.name, str_value))
return '\n'.join(lines)
2018-02-19 04:54:12 +01:00
def __iter__(self):
"""Yield ScopedValue elements.
This yields in "normal" order, i.e. global and then first-set settings
first.
"""
yield from self._values
2018-02-19 06:25:28 +01:00
def __bool__(self):
"""Check whether this value is customized."""
return bool(self._values)
def _check_pattern_support(self, arg):
"""Make sure patterns are supported if one was given."""
if arg is not None and not self.opt.supports_pattern:
raise configexc.NoPatternError(self.opt.name)
2018-02-19 04:54:12 +01:00
def add(self, value, pattern=None):
"""Add a value with the given pattern to the list of values."""
self._check_pattern_support(pattern)
self.remove(pattern)
2018-02-19 04:54:12 +01:00
scoped = ScopedValue(value, pattern)
self._values.append(scoped)
def remove(self, pattern=None):
2018-02-20 07:11:23 +01:00
"""Remove the value with the given pattern.
If a matching pattern was removed, True is returned.
If no matching pattern was found, False is returned.
"""
self._check_pattern_support(pattern)
old_len = len(self._values)
2018-02-19 04:54:12 +01:00
self._values = [v for v in self._values if v.pattern != pattern]
return old_len != len(self._values)
2018-02-19 04:54:12 +01:00
2018-02-19 05:08:17 +01:00
def clear(self):
"""Clear all customization for this value."""
self._values = []
def _get_fallback(self, fallback):
2018-02-19 04:54:12 +01:00
"""Get the fallback global/default value."""
for scoped in self._values:
2018-02-19 04:54:12 +01:00
if scoped.pattern is None:
return scoped.value
if fallback:
return self.opt.default
else:
return UNSET
2018-02-19 04:54:12 +01:00
def get_for_url(self, url=None, *, fallback=True):
"""Get a config value, falling back when needed.
This first tries to find a value matching the URL (if given).
If there's no match:
With fallback=True, the global/default setting is returned.
With fallback=False, UNSET is returned.
"""
self._check_pattern_support(url)
2018-02-19 04:54:12 +01:00
if url is not None:
for scoped in reversed(self._values):
if scoped.pattern is not None and scoped.pattern.matches(url):
return scoped.value
if not fallback:
return UNSET
return self._get_fallback(fallback)
2018-02-19 04:54:12 +01:00
def get_for_pattern(self, pattern, *, fallback=True):
"""Get a value only if it's been overridden for the given pattern.
This is useful when showing values to the user.
If there's no match:
With fallback=True, the global/default setting is returned.
With fallback=False, UNSET is returned.
"""
self._check_pattern_support(pattern)
2018-02-19 04:54:12 +01:00
if pattern is not None:
for scoped in reversed(self._values):
if scoped.pattern == pattern:
return scoped.value
if not fallback:
return UNSET
return self._get_fallback(fallback)